From 2c254105946a8f2d83fbc0d89f3f6a5fc7903c4f Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 12 Apr 2024 12:49:53 +0200 Subject: [PATCH 01/11] fix: obey hover `markup kind` capability --- .../internal/metals/ClientConfiguration.scala | 13 +++ .../metals/InlayHintResolveProvider.scala | 7 +- .../internal/metals/MetalsLspService.scala | 3 +- .../java/scala/meta/pc/HoverContentType.java | 16 +++ .../java/scala/meta/pc/HoverSignature.java | 4 + .../scala/meta/internal/pc/JavaHover.scala | 22 +++-- .../mtags/CommonMtagsEnrichments.scala | 7 +- .../scala/meta/internal/pc/HoverMarkup.scala | 98 ++++++++++++------- .../scala/meta/internal/pc/ItemResolver.scala | 2 +- .../scala/meta/internal/pc/ScalaHover.scala | 13 ++- .../internal/pc/SignatureHelpProvider.scala | 7 +- .../main/scala/tests/pc/BaseHoverSuite.scala | 6 +- .../scala/tests/hover/HoverDocSuite.scala | 23 +++++ .../scala/tests/hover/HoverTermSuite.scala | 35 +++++++ .../scala/tests/pc/BaseJavaHoverSuite.scala | 7 +- 15 files changed, 203 insertions(+), 60 deletions(-) create mode 100644 mtags-interfaces/src/main/java/scala/meta/pc/HoverContentType.java diff --git a/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala index 537f41843b2..d9b049c4d0f 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala @@ -3,6 +3,7 @@ package scala.meta.internal.metals import scala.meta.internal.metals.Configs.GlobSyntaxConfig import scala.meta.internal.metals.config.DoctorFormat import scala.meta.internal.metals.config.StatusBarState +import scala.meta.pc.HoverContentType import org.eclipse.lsp4j.ClientCapabilities import org.eclipse.lsp4j.InitializeParams @@ -161,6 +162,18 @@ final class ClientConfiguration( } yield true }.getOrElse(false) + def hoverContentType(): HoverContentType = + (for { + capabilities <- clientCapabilities + textDocumentCapabilities <- Option(capabilities.getTextDocument()) + hoverCapabilities <- Option(textDocumentCapabilities.getHover()) + contentTypes <- Option(hoverCapabilities.getContentFormat()) + } yield { + if (contentTypes.contains(HoverContentType.MARKDOWN.toString())) + HoverContentType.MARKDOWN + else HoverContentType.PLAINTEXT + }).getOrElse(HoverContentType.MARKDOWN) + def isTreeViewProvider(): Boolean = extract( initializationOptions.treeViewProvider, diff --git a/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala index 1803d1e1133..4cacbdf7c60 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala @@ -16,6 +16,7 @@ import org.eclipse.{lsp4j => l} final class InlayHintResolveProvider( definitionProvider: DefinitionProvider, compilers: Compilers, + config: ClientConfiguration, )(implicit ec: ExecutionContextExecutorService, rc: ReportContext) { def resolve( inlayHint: InlayHint, @@ -69,7 +70,11 @@ final class InlayHintResolveProvider( ) compilers.hover(hoverParams, token).map { hover => hover - .foreach(h => labelPart.setTooltip(h.toLsp().getContents().getRight())) + .foreach(h => + labelPart.setTooltip( + h.toLsp(config.hoverContentType()).getContents().getRight() + ) + ) labelPart } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index ef900f8aeff..ded95ea9c7c 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -755,6 +755,7 @@ class MetalsLspService( new InlayHintResolveProvider( definitionProvider, compilers, + clientConfig, ) val doctor: Doctor = new Doctor( @@ -1455,7 +1456,7 @@ class MetalsLspService( CancelTokens.future { token => compilers .hover(params, token) - .map(_.map(_.toLsp())) + .map(_.map(_.toLsp(clientConfig.hoverContentType()))) .map( _.orElse { val path = params.textDocument.getUri.toAbsolutePath diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/HoverContentType.java b/mtags-interfaces/src/main/java/scala/meta/pc/HoverContentType.java new file mode 100644 index 00000000000..57b28de9522 --- /dev/null +++ b/mtags-interfaces/src/main/java/scala/meta/pc/HoverContentType.java @@ -0,0 +1,16 @@ +package scala.meta.pc; + +public enum HoverContentType { + MARKDOWN("markdown"), + PLAINTEXT("plaintext"); + + private final String name; + HoverContentType(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java b/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java index 093bdb37cb6..c0143537cd9 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java @@ -6,8 +6,12 @@ import java.util.Optional; public interface HoverSignature { + @Deprecated Hover toLsp(); Optional signature(); Optional getRange(); HoverSignature withRange(Range range); + default Hover toLsp(HoverContentType contentType) { + return toLsp(); + } } diff --git a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala index c472eb2f788..a780cff8d69 100644 --- a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala +++ b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala @@ -3,6 +3,8 @@ package scala.meta.internal.pc import java.util.Optional import scala.meta.internal.mtags.CommonMtagsEnrichments._ +import scala.meta.pc.HoverContentType +import scala.meta.pc.HoverContentType.MARKDOWN import scala.meta.pc.HoverSignature import org.eclipse.lsp4j @@ -17,15 +19,17 @@ case class JavaHover( def signature(): Optional[String] = symbolSignature.asJava - def toLsp(): lsp4j.Hover = { - val markdown = - HoverMarkup.javaHoverMarkup( - expressionType.getOrElse(""), - symbolSignature.getOrElse(""), - docstring.getOrElse(""), - forceExpressionType - ) - new lsp4j.Hover(markdown.toMarkupContent, range.orNull) + def toLsp(): lsp4j.Hover = toLsp(MARKDOWN) + + override def toLsp(contentType: HoverContentType): lsp4j.Hover = { + val markup = HoverMarkup.javaHoverMarkup( + expressionType.getOrElse(""), + symbolSignature.getOrElse(""), + docstring.getOrElse(""), + forceExpressionType, + markdown = contentType == MARKDOWN + ) + new lsp4j.Hover(markup.toMarkupContent(contentType), range.orNull) } def getRange(): Optional[lsp4j.Range] = range.asJava diff --git a/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala b/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala index 88ead67fea7..838a77e99d6 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala @@ -31,6 +31,7 @@ import org.eclipse.lsp4j.CompletionItem import org.eclipse.lsp4j.MarkupContent import org.eclipse.lsp4j.jsonrpc.messages.{Either => JEither} import org.eclipse.{lsp4j => l} +import scala.meta.pc.HoverContentType object CommonMtagsEnrichments extends CommonMtagsEnrichments {} trait CommonMtagsEnrichments { @@ -275,9 +276,11 @@ trait CommonMtagsEnrichments { start >= 0 && doc.startsWith(value, start) } - def toMarkupContent: l.MarkupContent = { + def toMarkupContent( + contentType: HoverContentType = HoverContentType.MARKDOWN + ): l.MarkupContent = { val content = new MarkupContent - content.setKind("markdown") + content.setKind(contentType.toString()) content.setValue(doc) content } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala index 9a7e2add956..d7d7edccbbf 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala @@ -19,38 +19,50 @@ object HoverMarkup { optSymbolSignature: Option[String], docstring: String, forceExpressionType: Boolean = false, - contextInfo: List[String] = Nil + contextInfo: List[String] = Nil, + markdown: Boolean = true ): String = { - val markdown = new StringBuilder() + val builder = new StringBuilder() + + def appendCode(title: Option[String], code: String) = { + title.foreach { title => + builder + .append(if (markdown) "**" else "") + .append(title) + .append(if (markdown) "**" else "") + .append(":\n") + } + builder + .append(if (markdown) "```scala\n" else "") + .append(code) + .append(if (markdown) "\n```" else "\n") + } + if (contextInfo.nonEmpty) { - markdown - .append("```scala\n") - .append(contextInfo.mkString("\n")) - .append("\n```\n\n") + appendCode(None, contextInfo.mkString("\n")) + builder.append("\n\n") } if (forceExpressionType || optSymbolSignature.isEmpty) { - markdown - .append( - if (optSymbolSignature.isDefined) "**Expression type**:\n" else "" - ) - .append("```scala\n") - .append(expressionType) - .append("\n```\n") + appendCode( + Option.when(optSymbolSignature.isDefined)("Expression type"), + expressionType + ) + builder.append("\n") } + optSymbolSignature.foreach { symbolSignature => if (symbolSignature.nonEmpty) { - markdown - .append(if (forceExpressionType) "**Symbol signature**:\n" else "") - .append("```scala\n") - .append(symbolSignature) - .append("\n```") + appendCode( + Option.when(forceExpressionType)("Symbol signature"), + symbolSignature + ) } } if (docstring.nonEmpty) - markdown + builder .append("\n") .append(docstring) - markdown.toString() + builder.toString() } private def trimBody(body: String) = @@ -73,27 +85,41 @@ object HoverMarkup { expressionType: String, symbolSignature: String, docstring: String, - forceExpressionType: Boolean = false + forceExpressionType: Boolean = false, + markdown: Boolean = true ): String = { - val markdown = new StringBuilder() - if (forceExpressionType) { - markdown - .append("**Expression type**:\n") - .append("```java\n") - .append(expressionType) - .append("\n```\n") + val builder = new StringBuilder() + + def addCode(title: Option[String], code: String) = { + title.foreach { title => + builder + .append(if (markdown) "**" else "") + .append(title) + .append(if (markdown) "**" else "") + .append(":\n") + } + builder + .append(if (markdown) "```java\n" else "") + .append(code) + .append(if (markdown) "\n```" else "") } - if (symbolSignature.nonEmpty) { - markdown - .append(if (forceExpressionType) "**Symbol signature**:\n" else "") - .append("```java\n") - .append(symbolSignature) - .append("\n```") + + if (forceExpressionType) { + addCode(Some("Expression type"), expressionType) + builder.append("\n") } + + if (symbolSignature.nonEmpty) + addCode( + Option.when(forceExpressionType)("Symbol signature"), + symbolSignature + ) + if (docstring.nonEmpty) - markdown + builder .append("\n") .append(docstring) - markdown.toString() + builder.toString() } + } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/ItemResolver.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/ItemResolver.scala index f8fa8ee1d8a..b41d5088cd5 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/ItemResolver.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/ItemResolver.scala @@ -72,7 +72,7 @@ trait ItemResolver { } } if (metalsConfig.isCompletionItemDocumentationEnabled) { - item.setDocumentation(docstring.toMarkupContent) + item.setDocumentation(docstring.toMarkupContent()) } item } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala index da79351dd61..aeac2052f10 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala @@ -3,6 +3,8 @@ package scala.meta.internal.pc import java.util.Optional import scala.meta.internal.mtags.CommonMtagsEnrichments._ +import scala.meta.pc.HoverContentType +import scala.meta.pc.HoverContentType.MARKDOWN import scala.meta.pc.HoverSignature import org.eclipse.lsp4j @@ -34,16 +36,19 @@ case class ScalaHover( def signature(): Optional[String] = symbolSignature.asJava - def toLsp(): lsp4j.Hover = { - val markdown = + def toLsp(): lsp4j.Hover = toLsp(HoverContentType.MARKDOWN) + + override def toLsp(contentType: HoverContentType): lsp4j.Hover = { + val markup = HoverMarkup( expressionType.getOrElse(""), symbolSignature, docstring.getOrElse(""), forceExpressionType, - contextInfo + contextInfo, + markdown = contentType == MARKDOWN ) - new lsp4j.Hover(markdown.toMarkupContent, range.orNull) + new lsp4j.Hover(markup.toMarkupContent(contentType), range.orNull) } def getRange(): Optional[lsp4j.Range] = range.asJava diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/SignatureHelpProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/SignatureHelpProvider.scala index 1f4e75a4565..8f2df9c7667 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/SignatureHelpProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/SignatureHelpProvider.scala @@ -604,7 +604,7 @@ class SignatureHelpProvider(val compiler: MetalsGlobal) { else label val lparam = new ParameterInformation(byNameLabel) if (metalsConfig.isSignatureHelpDocumentationEnabled) { - lparam.setDocumentation(docstring.toMarkupContent) + lparam.setDocumentation(docstring.toMarkupContent()) } if (isActiveSignature && t.activeArg.matches(param, i, j)) { arg(i, j) match { @@ -617,7 +617,8 @@ class SignatureHelpProvider(val compiler: MetalsGlobal) { metalsConfig.isSignatureHelpDocumentationEnabled ) { lparam.setDocumentation( - ("```scala\n" + tpe + "\n```\n" + docstring).toMarkupContent + ("```scala\n" + tpe + "\n```\n" + docstring) + .toMarkupContent() ) } case _ => @@ -640,7 +641,7 @@ class SignatureHelpProvider(val compiler: MetalsGlobal) { ) if (metalsConfig.isSignatureHelpDocumentationEnabled) { signatureInformation.setDocumentation( - printer.methodDocstring.toMarkupContent + printer.methodDocstring.toMarkupContent() ) } signatureInformation.setParameters( diff --git a/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala b/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala index 602f4f83f76..1d9efc3d656 100644 --- a/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala +++ b/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala @@ -6,6 +6,7 @@ import scala.meta.XtensionSyntax import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.mtags.CommonMtagsEnrichments.XtensionOptionalJava +import scala.meta.pc.HoverContentType import munit.Location import munit.TestOptions @@ -24,7 +25,8 @@ abstract class BaseHoverSuite expected: String, includeRange: Boolean = false, automaticPackage: Boolean = true, - compat: Map[String, String] = Map.empty + compat: Map[String, String] = Map.empty, + contentType: HoverContentType = HoverContentType.MARKDOWN )(implicit loc: Location): Unit = { test(testOpt) { val filename = "Hover.scala" @@ -46,7 +48,7 @@ abstract class BaseHoverSuite .hover(pcParams) .get() .asScala - .map(_.toLsp()) + .map(_.toLsp(contentType)) val obtained: String = renderAsString(code, hover, includeRange) assertNoDiff( obtained, diff --git a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala index 4964378dbb8..bd6e751a1da 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala @@ -1,5 +1,7 @@ package tests.hover +import scala.meta.pc.HoverContentType + import tests.pc.BaseHoverSuite class HoverDocSuite extends BaseHoverSuite { @@ -527,4 +529,25 @@ class HoverDocSuite extends BaseHoverSuite { |""".stripMargin ) ) + + check( + "basic-plaintext", + """| + |/** + | * Some docstring + | */ + |case class Alpha(x: Int) { + |} + | + |object Main { + | val x = <> + |} + |""".stripMargin, + """|def apply(x: Int): Alpha + | + |Some docstring + | + |""".stripMargin, + contentType = HoverContentType.PLAINTEXT + ) } diff --git a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala index 38f5f11a210..411b94b595d 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala @@ -1,5 +1,7 @@ package tests.hover +import scala.meta.pc.HoverContentType + import tests.pc.BaseHoverSuite class HoverTermSuite extends BaseHoverSuite { @@ -718,4 +720,37 @@ class HoverTermSuite extends BaseHoverSuite { |""".stripMargin, "".stripMargin ) + + check( + "app-plaintext", + """|object Main extends <>{} + |""".stripMargin, + "abstract trait App: App", + compat = Map( + "3" -> "trait App: App" + ), + contentType = HoverContentType.PLAINTEXT + ) + + check( + "function-chain4-plaintext", + """ + |trait Consumer { + | def subConsumer[T](i: T): T + | def consume(value: Int)(n: Int): Unit + |} + | + |object O { + | val consumer: Consumer = ??? + | List(1).foreach(<>.consume(1)) + |} + |""".stripMargin, + """|Expression type: + |Consumer + | + |Symbol signature: + |def subConsumer[T](i: T): T + |""".stripMargin, + contentType = HoverContentType.PLAINTEXT + ) } diff --git a/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala b/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala index c0c031d99c3..18a0064e991 100644 --- a/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala +++ b/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala @@ -6,6 +6,7 @@ import java.nio.file.Paths import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.mtags.MtagsEnrichments._ +import scala.meta.pc.HoverContentType import munit.Location import munit.TestOptions @@ -43,7 +44,11 @@ class BaseJavaHoverSuite extends BaseJavaPCSuite with TestHovers { .get() val obtained: String = - renderAsString(code, hover.asScala.map(_.toLsp), includeRange) + renderAsString( + code, + hover.asScala.map(_.toLsp(HoverContentType.MARKDOWN)), + includeRange, + ) assertNoDiff( obtained, From 13c2290d10ec348ea443a435eda4837787e2f18b Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 12 Apr 2024 12:59:53 +0200 Subject: [PATCH 02/11] adjust for 2.11 --- .../scala/meta/internal/mtags/CommonMtagsEnrichments.scala | 2 +- .../src/main/scala/scala/meta/internal/pc/HoverMarkup.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala b/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala index 838a77e99d6..c54a1eafc9f 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala @@ -21,6 +21,7 @@ import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.pc.CompletionItemData import scala.meta.internal.pc.RangeOffset +import scala.meta.pc.HoverContentType import scala.meta.pc.OffsetParams import scala.meta.pc.RangeParams import scala.meta.pc.VirtualFileParams @@ -31,7 +32,6 @@ import org.eclipse.lsp4j.CompletionItem import org.eclipse.lsp4j.MarkupContent import org.eclipse.lsp4j.jsonrpc.messages.{Either => JEither} import org.eclipse.{lsp4j => l} -import scala.meta.pc.HoverContentType object CommonMtagsEnrichments extends CommonMtagsEnrichments {} trait CommonMtagsEnrichments { diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala index d7d7edccbbf..7d5c030bd8b 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala @@ -44,7 +44,7 @@ object HoverMarkup { } if (forceExpressionType || optSymbolSignature.isEmpty) { appendCode( - Option.when(optSymbolSignature.isDefined)("Expression type"), + if (optSymbolSignature.isDefined) Some("Expression type") else None, expressionType ) builder.append("\n") @@ -53,7 +53,7 @@ object HoverMarkup { optSymbolSignature.foreach { symbolSignature => if (symbolSignature.nonEmpty) { appendCode( - Option.when(forceExpressionType)("Symbol signature"), + if (forceExpressionType) Some("Symbol signature") else None, symbolSignature ) } @@ -111,7 +111,7 @@ object HoverMarkup { if (symbolSignature.nonEmpty) addCode( - Option.when(forceExpressionType)("Symbol signature"), + if (forceExpressionType) Some("Symbol signature") else None, symbolSignature ) From ff2e2df001abbc35555d71f3bbba2f1607df4a10 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 12 Apr 2024 18:47:19 +0200 Subject: [PATCH 03/11] plaintext for scaladoc --- .../bench/ClasspathOnlySymbolSearch.scala | 7 ++ .../internal/metals/ClientConfiguration.scala | 12 +-- .../meta/internal/metals/Compilers.scala | 6 +- .../metals/InlayHintResolveProvider.scala | 2 +- .../internal/metals/MetalsLspService.scala | 2 +- .../internal/metals/MetalsSymbolSearch.scala | 10 +- .../metals/StandaloneSymbolSearch.scala | 14 ++- ...HoverContentType.java => ContentType.java} | 4 +- .../java/scala/meta/pc/HoverSignature.java | 5 +- .../scala/meta/pc/PresentationCompiler.java | 9 ++ .../main/java/scala/meta/pc/SymbolSearch.java | 8 ++ .../scala/meta/internal/pc/JavaHover.scala | 11 +-- .../meta/internal/pc/JavaHoverProvider.scala | 15 ++- .../pc/JavaPresentationCompiler.scala | 11 ++- .../mtags/CommonMtagsEnrichments.scala | 4 +- .../meta/internal/pc/EmptySymbolSearch.scala | 7 ++ .../scala/meta/internal/pc/ScalaHover.scala | 17 ++-- .../meta/internal/pc/HoverProvider.scala | 20 ++-- .../scala/meta/internal/pc/MetalsGlobal.scala | 8 +- .../pc/ScalaPresentationCompiler.scala | 10 +- .../internal/mtags/MtagsEnrichments.scala | 4 +- .../meta/internal/pc/HoverProvider.scala | 11 ++- .../pc/ScalaPresentationCompiler.scala | 10 +- .../meta/internal/metals/Docstrings.scala | 56 +++++++---- .../meta/internal/metals/JavadocIndexer.scala | 43 +++++--- .../internal/metals/ScaladocIndexer.scala | 36 +++++-- .../docstrings/PlaintextGenerator.scala | 98 +++++++++++++++++++ .../main/scala/tests/pc/BaseHoverSuite.scala | 8 +- .../scala/tests/hover/HoverDocSuite.scala | 52 +++++++++- .../scala/tests/hover/HoverTermSuite.scala | 6 +- .../InterruptPresentationCompilerSuite.scala | 3 +- .../scala/tests/pc/BaseJavaHoverSuite.scala | 6 +- .../scala/tests/TestingSymbolSearch.scala | 11 ++- 33 files changed, 412 insertions(+), 114 deletions(-) rename mtags-interfaces/src/main/java/scala/meta/pc/{HoverContentType.java => ContentType.java} (75%) create mode 100644 mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala diff --git a/metals-bench/src/main/scala/bench/ClasspathOnlySymbolSearch.scala b/metals-bench/src/main/scala/bench/ClasspathOnlySymbolSearch.scala index 9b30e3ba587..a5c524f0f31 100644 --- a/metals-bench/src/main/scala/bench/ClasspathOnlySymbolSearch.scala +++ b/metals-bench/src/main/scala/bench/ClasspathOnlySymbolSearch.scala @@ -6,6 +6,7 @@ import java.{util => ju} import scala.meta.internal.metals.ClasspathSearch import scala.meta.internal.metals.WorkspaceSymbolQuery +import scala.meta.pc.ContentType import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch @@ -26,6 +27,12 @@ class ClasspathOnlySymbolSearch(classpath: ClasspathSearch) ): Optional[SymbolDocumentation] = Optional.empty() + override def documentation( + symbol: String, + parents: ParentSymbols, + docstringContentType: ContentType, + ): Optional[SymbolDocumentation] = Optional.empty() + def definition(symbol: String, source: URI): ju.List[Location] = ju.Collections.emptyList() diff --git a/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala index d9b049c4d0f..fa676c7262e 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/ClientConfiguration.scala @@ -3,7 +3,7 @@ package scala.meta.internal.metals import scala.meta.internal.metals.Configs.GlobSyntaxConfig import scala.meta.internal.metals.config.DoctorFormat import scala.meta.internal.metals.config.StatusBarState -import scala.meta.pc.HoverContentType +import scala.meta.pc.ContentType import org.eclipse.lsp4j.ClientCapabilities import org.eclipse.lsp4j.InitializeParams @@ -162,17 +162,17 @@ final class ClientConfiguration( } yield true }.getOrElse(false) - def hoverContentType(): HoverContentType = + def hoverContentType(): ContentType = (for { capabilities <- clientCapabilities textDocumentCapabilities <- Option(capabilities.getTextDocument()) hoverCapabilities <- Option(textDocumentCapabilities.getHover()) contentTypes <- Option(hoverCapabilities.getContentFormat()) } yield { - if (contentTypes.contains(HoverContentType.MARKDOWN.toString())) - HoverContentType.MARKDOWN - else HoverContentType.PLAINTEXT - }).getOrElse(HoverContentType.MARKDOWN) + if (contentTypes.contains(ContentType.MARKDOWN.toString())) + ContentType.MARKDOWN + else ContentType.PLAINTEXT + }).getOrElse(ContentType.MARKDOWN) def isTreeViewProvider(): Boolean = extract( 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 4a25ff48c77..268a23b67d5 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -726,8 +726,10 @@ class Compilers( token: CancelToken, ): Future[Option[HoverSignature]] = { withPCAndAdjustLsp(params) { (pc, pos, adjust) => - pc.hover(CompilerRangeParamsUtils.offsetOrRange(pos, token)) - .asScala + pc.hover( + CompilerRangeParamsUtils.offsetOrRange(pos, token), + config.hoverContentType(), + ).asScala .map(_.asScala.map { hover => adjust.adjustHoverResp(hover) }) } }.getOrElse(Future.successful(None)) diff --git a/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala index 4cacbdf7c60..839ccdb5fc4 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala @@ -72,7 +72,7 @@ final class InlayHintResolveProvider( hover .foreach(h => labelPart.setTooltip( - h.toLsp(config.hoverContentType()).getContents().getRight() + h.toLsp().getContents().getRight() ) ) labelPart diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index ded95ea9c7c..2072d86f519 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -1456,7 +1456,7 @@ class MetalsLspService( CancelTokens.future { token => compilers .hover(params, token) - .map(_.map(_.toLsp(clientConfig.hoverContentType()))) + .map(_.map(_.toLsp())) .map( _.orElse { val path = params.textDocument.getUri.toAbsolutePath diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsSymbolSearch.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsSymbolSearch.scala index ce1364f499c..c62598657c8 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsSymbolSearch.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsSymbolSearch.scala @@ -9,6 +9,7 @@ import scala.collection.concurrent.TrieMap import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.mtags.Mtags import scala.meta.io.AbsolutePath +import scala.meta.pc.ContentType import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch @@ -40,7 +41,14 @@ class MetalsSymbolSearch( symbol: String, parents: ParentSymbols, ): Optional[SymbolDocumentation] = - docs.documentation(symbol, parents) + documentation(symbol, parents, ContentType.MARKDOWN) + + override def documentation( + symbol: String, + parents: ParentSymbols, + contentType: ContentType, + ): Optional[SymbolDocumentation] = + docs.documentation(symbol, parents, contentType) def definition(symbol: String, source: URI): ju.List[Location] = { val sourcePath = Option(source).map(AbsolutePath.fromAbsoluteUri) diff --git a/metals/src/main/scala/scala/meta/internal/metals/StandaloneSymbolSearch.scala b/metals/src/main/scala/scala/meta/internal/metals/StandaloneSymbolSearch.scala index fef7b08d78d..9546ca0c6b6 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/StandaloneSymbolSearch.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/StandaloneSymbolSearch.scala @@ -11,6 +11,7 @@ import scala.meta.internal.mtags.Mtags import scala.meta.internal.mtags.OnDemandSymbolIndex import scala.meta.internal.parsing.Trees import scala.meta.io.AbsolutePath +import scala.meta.pc.ContentType import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch @@ -64,12 +65,21 @@ class StandaloneSymbolSearch( def documentation( symbol: String, parents: ParentSymbols, + ): ju.Optional[SymbolDocumentation] = + documentation(symbol, parents, docstringContentType = ContentType.MARKDOWN) + + override def documentation( + symbol: String, + parents: ParentSymbols, + docstringContentType: ContentType, ): ju.Optional[SymbolDocumentation] = docs - .documentation(symbol, parents) + .documentation(symbol, parents, docstringContentType) .asScala .orElse( - workspaceFallback.flatMap(_.documentation(symbol, parents).asScala) + workspaceFallback.flatMap( + _.documentation(symbol, parents, docstringContentType).asScala + ) ) .asJava diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/HoverContentType.java b/mtags-interfaces/src/main/java/scala/meta/pc/ContentType.java similarity index 75% rename from mtags-interfaces/src/main/java/scala/meta/pc/HoverContentType.java rename to mtags-interfaces/src/main/java/scala/meta/pc/ContentType.java index 57b28de9522..99e5b8c0f90 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/HoverContentType.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/ContentType.java @@ -1,11 +1,11 @@ package scala.meta.pc; -public enum HoverContentType { +public enum ContentType { MARKDOWN("markdown"), PLAINTEXT("plaintext"); private final String name; - HoverContentType(String name) { + ContentType(String name) { this.name = name; } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java b/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java index c0143537cd9..f8762d086ee 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java @@ -6,12 +6,11 @@ import java.util.Optional; public interface HoverSignature { - @Deprecated Hover toLsp(); Optional signature(); Optional getRange(); HoverSignature withRange(Range range); - default Hover toLsp(HoverContentType contentType) { - return toLsp(); + default ContentType contentType() { + return ContentType.MARKDOWN; } } diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java index 734cfc26e91..2d4306e6f69 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -80,8 +80,17 @@ public CompletableFuture> semanticTokens(VirtualFileParams params) { * Returns the type of the expression at the given position along with the * symbol of the referenced symbol. */ + @Deprecated public abstract CompletableFuture> hover(OffsetParams params); + /** + * Returns the type of the expression at the given position along with the + * symbol of the referenced symbol. + */ + public CompletableFuture> hover(OffsetParams params, ContentType contentType) { + return hover(params); + } + /** * Checks if the symbol at given position can be renamed using presentation * compiler. Returns Some(range) if symbol is defined inside a method or the diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/SymbolSearch.java b/mtags-interfaces/src/main/java/scala/meta/pc/SymbolSearch.java index 7dce02f3fa7..42f9b04ba83 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/SymbolSearch.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/SymbolSearch.java @@ -13,8 +13,16 @@ public interface SymbolSearch { /** * Returns the documentation of this symbol, if any. */ + @Deprecated Optional documentation(String symbol, ParentSymbols parents); + /** + * Returns the documentation of this symbol, if any. + */ + default Optional documentation(String symbol, ParentSymbols parents, ContentType contentType) { + return documentation(symbol, parents); + } + /** * Returns the definition of this symbol, if any. */ diff --git a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala index a780cff8d69..3e1bb16e996 100644 --- a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala +++ b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHover.scala @@ -3,8 +3,8 @@ package scala.meta.internal.pc import java.util.Optional import scala.meta.internal.mtags.CommonMtagsEnrichments._ -import scala.meta.pc.HoverContentType -import scala.meta.pc.HoverContentType.MARKDOWN +import scala.meta.pc.ContentType +import scala.meta.pc.ContentType.MARKDOWN import scala.meta.pc.HoverSignature import org.eclipse.lsp4j @@ -14,14 +14,13 @@ case class JavaHover( symbolSignature: Option[String] = None, docstring: Option[String] = None, forceExpressionType: Boolean = false, - range: Option[lsp4j.Range] = None + range: Option[lsp4j.Range] = None, + override val contentType: ContentType ) extends HoverSignature { def signature(): Optional[String] = symbolSignature.asJava - def toLsp(): lsp4j.Hover = toLsp(MARKDOWN) - - override def toLsp(contentType: HoverContentType): lsp4j.Hover = { + override def toLsp(): lsp4j.Hover = { val markup = HoverMarkup.javaHoverMarkup( expressionType.getOrElse(""), symbolSignature.getOrElse(""), diff --git a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHoverProvider.scala b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHoverProvider.scala index b793bde3986..3d8030a78e1 100644 --- a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHoverProvider.scala +++ b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaHoverProvider.scala @@ -23,6 +23,7 @@ import scala.jdk.CollectionConverters.SeqHasAsJava import scala.jdk.OptionConverters.RichOptional import scala.meta.internal.mtags.CommonMtagsEnrichments._ +import scala.meta.pc.ContentType import scala.meta.pc.HoverSignature import scala.meta.pc.OffsetParams import scala.meta.pc.ParentSymbols @@ -33,7 +34,8 @@ import com.sun.source.util.Trees class JavaHoverProvider( compiler: JavaMetalsGlobal, - params: OffsetParams + params: OffsetParams, + contentType: ContentType ) { def hover(): Option[HoverSignature] = params match { @@ -82,7 +84,13 @@ class JavaHoverProvider( case _ => None } - sig.map(s => JavaHover(symbolSignature = Some(s), docstring = Some(docs))) + sig.map(s => + JavaHover( + symbolSignature = Some(s), + docstring = Some(docs), + contentType = contentType + ) + ) } private def typeHover(t: TypeMirror): String = @@ -193,7 +201,8 @@ class JavaHoverProvider( case _ => util.Collections.emptyList[String] } } - } + }, + contentType ) .toScala .map(_.docstring()) diff --git a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala index f3163bec969..c4cf55e14a4 100644 --- a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala +++ b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala @@ -14,6 +14,7 @@ import scala.concurrent.ExecutionContextExecutor import scala.jdk.CollectionConverters._ import scala.meta.pc.AutoImportsResult +import scala.meta.pc.ContentType import scala.meta.pc.DefinitionResult import scala.meta.pc.HoverSignature import scala.meta.pc.InlayHintsParams @@ -68,14 +69,20 @@ case class JavaPresentationCompiler( CompletableFuture.completedFuture(new SignatureHelp()) override def hover( - params: OffsetParams + params: OffsetParams, + contentType: ContentType ): CompletableFuture[Optional[HoverSignature]] = CompletableFuture.completedFuture( Optional.ofNullable( - new JavaHoverProvider(javaCompiler, params).hover().orNull + new JavaHoverProvider(javaCompiler, params, contentType).hover().orNull ) ) + override def hover( + params: OffsetParams + ): CompletableFuture[Optional[HoverSignature]] = + hover(params, ContentType.MARKDOWN) + override def rename( params: OffsetParams, name: String diff --git a/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala b/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala index c54a1eafc9f..167cc997dd5 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/mtags/CommonMtagsEnrichments.scala @@ -21,7 +21,7 @@ import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.pc.CompletionItemData import scala.meta.internal.pc.RangeOffset -import scala.meta.pc.HoverContentType +import scala.meta.pc.ContentType import scala.meta.pc.OffsetParams import scala.meta.pc.RangeParams import scala.meta.pc.VirtualFileParams @@ -277,7 +277,7 @@ trait CommonMtagsEnrichments { doc.startsWith(value, start) } def toMarkupContent( - contentType: HoverContentType = HoverContentType.MARKDOWN + contentType: ContentType = ContentType.MARKDOWN ): l.MarkupContent = { val content = new MarkupContent content.setKind(contentType.toString()) diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/EmptySymbolSearch.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/EmptySymbolSearch.scala index 7eaad24cf43..68809f290b3 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/EmptySymbolSearch.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/EmptySymbolSearch.scala @@ -4,6 +4,7 @@ import java.net.URI import java.util.Optional import java.{util => ju} +import scala.meta.pc.ContentType import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch @@ -44,4 +45,10 @@ object EmptySymbolSearch extends SymbolSearch { parents: ParentSymbols ): Optional[SymbolDocumentation] = Optional.empty() + + override def documentation( + symbol: String, + parents: ParentSymbols, + docstringContentType: ContentType + ): Optional[SymbolDocumentation] = Optional.empty() } diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala index aeac2052f10..f5b23e0e3a5 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala @@ -3,8 +3,8 @@ package scala.meta.internal.pc import java.util.Optional import scala.meta.internal.mtags.CommonMtagsEnrichments._ -import scala.meta.pc.HoverContentType -import scala.meta.pc.HoverContentType.MARKDOWN +import scala.meta.pc.ContentType +import scala.meta.pc.ContentType.MARKDOWN import scala.meta.pc.HoverSignature import org.eclipse.lsp4j @@ -15,7 +15,8 @@ case class ScalaHover( docstring: Option[String] = None, forceExpressionType: Boolean = false, range: Option[lsp4j.Range] = None, - contextInfo: List[String] // e.g. info about rename imports + contextInfo: List[String], // e.g. info about rename imports + override val contentType: ContentType ) extends HoverSignature { def this( @@ -31,14 +32,13 @@ case class ScalaHover( docstring, forceExpressionType, range, - contextInfo = Nil + contextInfo = Nil, + contentType = ContentType.MARKDOWN ) def signature(): Optional[String] = symbolSignature.asJava - def toLsp(): lsp4j.Hover = toLsp(HoverContentType.MARKDOWN) - - override def toLsp(contentType: HoverContentType): lsp4j.Hover = { + def toLsp(): lsp4j.Hover = { val markup = HoverMarkup( expressionType.getOrElse(""), @@ -60,7 +60,8 @@ case class ScalaHover( docstring, forceExpressionType, Some(range), - contextInfo + contextInfo, + contentType ) } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/HoverProvider.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/HoverProvider.scala index fd981011d21..714e6f3e75e 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/HoverProvider.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/HoverProvider.scala @@ -6,11 +6,16 @@ import scala.reflect.internal.{Flags => gf} import scala.meta.internal.metals.Report import scala.meta.internal.metals.ReportContext import scala.meta.internal.mtags.MtagsEnrichments._ +import scala.meta.pc.ContentType import scala.meta.pc.HoverSignature import scala.meta.pc.OffsetParams import scala.meta.pc.RangeParams -class HoverProvider(val compiler: MetalsGlobal, params: OffsetParams)(implicit +class HoverProvider( + val compiler: MetalsGlobal, + params: OffsetParams, + contentType: ContentType +)(implicit reportContext: ReportContext ) { import compiler._ @@ -214,9 +219,9 @@ class HoverProvider(val compiler: MetalsGlobal, params: OffsetParams)(implicit def docstring = if (metalsConfig.isHoverDocumentationEnabled) { - symbolDocumentation(symbol) + symbolDocumentation(symbol, contentType) .filter(docs => !docs.docstring().isEmpty()) - .orElse(symbolDocumentation(symbol.companion)) + .orElse(symbolDocumentation(symbol.companion, contentType)) .fold("")(_.docstring()) } else { "" @@ -239,7 +244,8 @@ class HoverProvider(val compiler: MetalsGlobal, params: OffsetParams)(implicit new ScalaHover( expressionType = Some(prettyType), range = lspRange, - contextInfo = history.getUsedRenamesInfo() + contextInfo = history.getUsedRenamesInfo(), + contentType = contentType ) ) } else if (symbol == null || tpe.typeSymbol.isAnonymousClass) None @@ -250,7 +256,8 @@ class HoverProvider(val compiler: MetalsGlobal, params: OffsetParams)(implicit s"${symbol.javaClassSymbol.keyString} ${symbol.fullName}" ), contextInfo = Nil, - docstring = if (symbol.hasModuleFlag) Some(docstring) else None + docstring = if (symbol.hasModuleFlag) Some(docstring) else None, + contentType = contentType ) ) } else { @@ -289,7 +296,8 @@ class HoverProvider(val compiler: MetalsGlobal, params: OffsetParams)(implicit forceExpressionType = pos.start != pos.end || !prettySignature.endsWith(prettyType), range = if (range.isRange) Some(range.toLsp) else None, - contextInfo = history.getUsedRenamesInfo() + contextInfo = history.getUsedRenamesInfo(), + contentType = contentType ) ) } diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala index 8a32df09888..e6079b8bcaf 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/MetalsGlobal.scala @@ -223,7 +223,10 @@ class MetalsGlobal( buffer.toList } - def symbolDocumentation(symbol: Symbol): Option[SymbolDocumentation] = { + def symbolDocumentation( + symbol: Symbol, + contentType: m.pc.ContentType = m.pc.ContentType.MARKDOWN + ): Option[SymbolDocumentation] = { def toSemanticdbSymbol(sym: Symbol) = compiler.semanticdbSymbol( if (!sym.isJava && sym.isPrimaryConstructor) sym.owner else sym @@ -243,7 +246,8 @@ class MetalsGlobal( parentSymbols.map(toSemanticdbSymbol).asJava } - } + }, + contentType ) if (documentation.isPresent) { diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 7fad8e3fc10..7ef3d54da56 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -28,6 +28,7 @@ import scala.meta.internal.metals.StdReportContext import scala.meta.internal.mtags.BuildInfo import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.pc.AutoImportsResult +import scala.meta.pc.ContentType import scala.meta.pc.DefinitionResult import scala.meta.pc.DisplayableException import scala.meta.pc.HoverSignature @@ -334,14 +335,21 @@ case class ScalaPresentationCompiler( override def hover( params: OffsetParams ): CompletableFuture[Optional[HoverSignature]] = + hover(params, ContentType.MARKDOWN) + + override def hover( + params: OffsetParams, + contentType: ContentType + ): CompletableFuture[Optional[HoverSignature]] = { compilerAccess.withNonInterruptableCompiler(Some(params))( Optional.empty[HoverSignature](), params.token ) { pc => Optional.ofNullable( - new HoverProvider(pc.compiler(), params).hover().orNull + new HoverProvider(pc.compiler(), params, contentType).hover().orNull ) } + } def definition(params: OffsetParams): CompletableFuture[DefinitionResult] = { compilerAccess.withNonInterruptableCompiler(Some(params))( diff --git a/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala b/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala index f2a124acd48..7cccab7d877 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/mtags/MtagsEnrichments.scala @@ -5,6 +5,7 @@ import scala.util.control.NonFatal import scala.meta.internal.jdk.CollectionConverters.* import scala.meta.internal.pc.SemanticdbSymbols +import scala.meta.pc.ContentType import scala.meta.pc.OffsetParams import scala.meta.pc.RangeParams import scala.meta.pc.SymbolDocumentation @@ -232,7 +233,7 @@ object MtagsEnrichments extends ScalametaCommonEnrichments: def stripBackticks: String = s.stripPrefix("`").stripSuffix("`") extension (search: SymbolSearch) - def symbolDocumentation(symbol: Symbol)(using + def symbolDocumentation(symbol: Symbol, contentType: ContentType = ContentType.MARKDOWN)(using Context ): Option[SymbolDocumentation] = def toSemanticdbSymbol(symbol: Symbol) = @@ -253,6 +254,7 @@ object MtagsEnrichments extends ScalametaCommonEnrichments: val documentation = search.documentation( sym, () => parentSymbols.map(toSemanticdbSymbol).toList.asJava, + contentType ) if documentation.isPresent then Some(documentation.get()) else None diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/HoverProvider.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/HoverProvider.scala index 2b9e1a2de57..94e0b894f2c 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/HoverProvider.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/HoverProvider.scala @@ -6,6 +6,7 @@ import scala.meta.internal.metals.Report import scala.meta.internal.metals.ReportContext import scala.meta.internal.mtags.MtagsEnrichments.* import scala.meta.internal.pc.printer.MetalsPrinter +import scala.meta.pc.ContentType import scala.meta.pc.HoverSignature import scala.meta.pc.OffsetParams import scala.meta.pc.SymbolSearch @@ -28,6 +29,7 @@ object HoverProvider: params: OffsetParams, driver: InteractiveDriver, search: SymbolSearch, + contentType: ContentType )(implicit reportContext: ReportContext): ju.Optional[HoverSignature] = val uri = params.uri val sourceFile = CompilerInterfaces.toSource(params.uri, params.text) @@ -107,10 +109,10 @@ object HoverProvider: skipCheckOnName, ) match case Nil => - fallbackToDynamics(path, printer) + fallbackToDynamics(path, printer, contentType) case (symbol, tpe) :: _ if symbol.name == nme.selectDynamic || symbol.name == nme.applyDynamic => - fallbackToDynamics(path, printer) + fallbackToDynamics(path, printer, contentType) case symbolTpes @ ((symbol, tpe) :: _) => val exprTpw = tpe.widenTermRefExpr.metalsDealias val hoverString = @@ -132,7 +134,7 @@ object HoverProvider: end hoverString val docString = symbolTpes - .flatMap(symTpe => search.symbolDocumentation(symTpe._1)) + .flatMap(symTpe => search.symbolDocumentation(symTpe._1, contentType)) .map(_.docstring) .mkString("\n") printer.expressionType(exprTpw) match @@ -151,6 +153,7 @@ object HoverProvider: docstring = Some(docString), forceExpressionType = forceExpressionType, contextInfo = printer.getUsedRenamesInfo(), + contentType = contentType ) ) case _ => @@ -166,6 +169,7 @@ object HoverProvider: private def fallbackToDynamics( path: List[Tree], printer: MetalsPrinter, + contentType: ContentType )(using Context): ju.Optional[HoverSignature] = path match case SelectDynamicExtractor(sel, n, name) => def findRefinement(tp: Type): Option[HoverSignature] = @@ -185,6 +189,7 @@ object HoverProvider: expressionType = Some(tpeString), symbolSignature = Some(s"$valOrDef $name$tpeString"), contextInfo = printer.getUsedRenamesInfo(), + contentType = contentType ) ) case RefinedType(parent, _, _) => diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 66ffd1a321e..e59bdb06355 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -358,14 +358,20 @@ case class ScalaPresentationCompiler( end selectionRange def hover( - params: OffsetParams + params: OffsetParams + ): CompletableFuture[Optional[HoverSignature]] = + hover(params, ContentType.MARKDOWN) + + override def hover( + params: OffsetParams, + contentType: ContentType ): CompletableFuture[ju.Optional[HoverSignature]] = compilerAccess.withNonInterruptableCompiler(Some(params))( ju.Optional.empty[HoverSignature](), params.token, ) { access => val driver = access.compiler() - HoverProvider.hover(params, driver, search) + HoverProvider.hover(params, driver, search, contentType) } end hover diff --git a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala index 0efcb4a4154..6ae6283618d 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala @@ -20,6 +20,7 @@ import scala.meta.internal.semanticdb.Language import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.semanticdb.SymbolOccurrence import scala.meta.io.AbsolutePath +import scala.meta.pc.ContentType import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation @@ -29,22 +30,23 @@ import scala.meta.pc.SymbolDocumentation * Handles both javadoc and scaladoc. */ class Docstrings(index: GlobalSymbolIndex) { - val cache = new TrieMap[String, SymbolDocumentation]() + val cache = new TrieMap[(String, ContentType), SymbolDocumentation]() private val logger = Logger.getLogger(classOf[Docstrings].getName) def documentation( symbol: String, - parents: ParentSymbols + parents: ParentSymbols, + contentType: ContentType ): Optional[SymbolDocumentation] = { - val result = cache.get(symbol) match { + val result = cache.get((symbol, contentType)) match { case Some(value) => if (value == EmptySymbolDocumentation) None else Some(value) case None => - indexSymbol(symbol) - val result = cache.get(symbol) + indexSymbol(symbol, contentType) + val result = cache.get((symbol, contentType)) if (result.isEmpty) - cache(symbol) = EmptySymbolDocumentation + cache((symbol, contentType)) = EmptySymbolDocumentation result } /* Fall back to parent javadocs/scaladocs if nothing is specified for the current symbol @@ -53,13 +55,14 @@ class Docstrings(index: GlobalSymbolIndex) { val resultWithParentDocs = result match { case Some(value: MetalsSymbolDocumentation) if value.docstring.isEmpty() => - Some(parentDocumentation(symbol, value, parents)) + Some(parentDocumentation(symbol, value, parents, contentType)) case None => Some( parentDocumentation( symbol, MetalsSymbolDocumentation.empty(symbol), - parents + parents, + contentType ) ) case _ => result @@ -70,16 +73,17 @@ class Docstrings(index: GlobalSymbolIndex) { def parentDocumentation( symbol: String, docs: MetalsSymbolDocumentation, - parents: ParentSymbols + parents: ParentSymbols, + contentType: ContentType ): SymbolDocumentation = { parents .parents() .asScala .flatMap { s => - if (cache.contains(s)) cache.get(s) + if (cache.contains((s, contentType))) cache.get((s, contentType)) else { - indexSymbol(s) - cache.get(s) + indexSymbol(s, contentType) + cache.get((s, contentType)) } } .find(_.docstring().nonEmpty) @@ -87,7 +91,7 @@ class Docstrings(index: GlobalSymbolIndex) { docs } { withDocs => val updated = docs.copy(docstring = withDocs.docstring()) - cache(symbol) = updated + cache((symbol, contentType)) = updated updated } } @@ -110,15 +114,18 @@ class Docstrings(index: GlobalSymbolIndex) { } } - private def cacheSymbol(doc: SymbolDocumentation): Unit = { - cache(doc.symbol()) = doc + private def cacheSymbol( + doc: SymbolDocumentation, + contentType: ContentType + ): Unit = { + cache((doc.symbol(), contentType)) = doc } - private def indexSymbol(symbol: String): Unit = { + private def indexSymbol(symbol: String, contentType: ContentType): Unit = { index.definition(Symbol(symbol)) match { case Some(defn) => try { - indexSymbolDefinition(defn) + indexSymbolDefinition(defn, contentType) } catch { case NonFatal(e) => logger.log(Level.SEVERE, defn.path.toURI.toString, e) @@ -127,14 +134,19 @@ class Docstrings(index: GlobalSymbolIndex) { } } - private def indexSymbolDefinition(defn: SymbolDefinition): Unit = { + private def indexSymbolDefinition( + defn: SymbolDefinition, + contentType: ContentType + ): Unit = { defn.path.toLanguage match { case Language.JAVA => JavadocIndexer - .foreach(defn.path.toInput)(cacheSymbol) + .foreach(defn.path.toInput, contentType)(cacheSymbol(_, contentType)) case Language.SCALA => ScaladocIndexer - .foreach(defn.path.toInput, defn.dialect)(cacheSymbol) + .foreach(defn.path.toInput, defn.dialect, contentType)( + cacheSymbol(_, contentType) + ) case _ => } } @@ -148,7 +160,9 @@ class Docstrings(index: GlobalSymbolIndex) { sinfo: SymbolInformation, owner: String ): Unit = { - cache.remove(occ.symbol) + for { + contentType <- ContentType.values() + } cache.remove((occ.symbol, contentType)) } } diff --git a/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala b/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala index 50cf8403135..adef897a473 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala @@ -12,6 +12,9 @@ import scala.meta.internal.mtags.JavaMtags import scala.meta.internal.semanticdb.Scala.Descriptor import scala.meta.internal.semanticdb.Scala.Symbols import scala.meta.internal.semanticdb.SymbolInformation +import scala.meta.pc.ContentType +import scala.meta.pc.ContentType.MARKDOWN +import scala.meta.pc.ContentType.PLAINTEXT import scala.meta.pc.SymbolDocumentation import com.thoughtworks.qdox.model.JavaAnnotatedElement @@ -27,7 +30,8 @@ import com.thoughtworks.qdox.model.JavaTypeVariable */ class JavadocIndexer( input: Input.VirtualFile, - fn: SymbolDocumentation => Unit + fn: SymbolDocumentation => Unit, + contentType: ContentType ) extends JavaMtags(input, includeMembers = true) { override def visitClass( cls: JavaClass, @@ -65,21 +69,26 @@ class JavadocIndexer( ) } - def markdown(e: JavaAnnotatedElement): String = { + def toContent(e: JavaAnnotatedElement): String = { val comment = Option(e.getComment).getOrElse("") - try MarkdownGenerator.fromDocstring(s"/**$comment\n*/", Map.empty) - catch { - case NonFatal(_) => - // The Scaladoc parser implementation uses fragile regexp processing which - // sometimes causes exceptions. - comment + contentType match { + case MARKDOWN => + try MarkdownGenerator.fromDocstring(s"/**$comment\n*/", Map.empty) + catch { + case NonFatal(_) => + // The Scaladoc parser implementation uses fragile regexp processing which + // sometimes causes exceptions. + comment + } + case PLAINTEXT => comment } } + def fromMethod(symbol: String, method: JavaMethod): SymbolDocumentation = { new MetalsSymbolDocumentation( symbol, method.getName, - markdown(method), + toContent(method), "", typeParameters(symbol, method, method.getTypeParameters), parameters(symbol, method, method.getParameters) @@ -92,7 +101,7 @@ class JavadocIndexer( new MetalsSymbolDocumentation( symbol, method.getName, - markdown(method), + toContent(method), "", typeParameters(symbol, method, method.getTypeParameters), Nil.asJava @@ -105,7 +114,7 @@ class JavadocIndexer( new MetalsSymbolDocumentation( symbol, method.getName, - markdown(method), + toContent(method), "", typeParameters(symbol, method, method.getTypeParameters), parameters(symbol, method, method.getParameters) @@ -159,14 +168,18 @@ class JavadocIndexer( } } object JavadocIndexer { - def all(input: Input.VirtualFile): List[SymbolDocumentation] = { + def all( + input: Input.VirtualFile, + contentType: ContentType + ): List[SymbolDocumentation] = { val buf = List.newBuilder[SymbolDocumentation] - foreach(input)(buf += _) + foreach(input, contentType)(buf += _) buf.result() } def foreach( - input: Input.VirtualFile + input: Input.VirtualFile, + contentType: ContentType )(fn: SymbolDocumentation => Unit): Unit = { - new JavadocIndexer(input, fn).indexRoot() + new JavadocIndexer(input, fn, contentType).indexRoot() } } diff --git a/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala b/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala index 7ac60e3769a..6000837aa3a 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala @@ -11,6 +11,9 @@ import scala.meta.internal.semanticdb.Scala.Descriptor import scala.meta.internal.semanticdb.Scala.Symbols import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.semanticdb.SymbolOccurrence +import scala.meta.pc.ContentType +import scala.meta.pc.ContentType.MARKDOWN +import scala.meta.pc.ContentType.PLAINTEXT import scala.meta.pc.SymbolDocumentation /** @@ -19,7 +22,8 @@ import scala.meta.pc.SymbolDocumentation class ScaladocIndexer( input: Input.VirtualFile, fn: SymbolDocumentation => Unit, - dialect: Dialect + dialect: Dialect, + contentType: ContentType ) extends ScalaMtags(input, dialect) { val defines: mutable.Map[String, String] = mutable.Map.empty[String, String] override def visitOccurrence( @@ -43,12 +47,23 @@ class ScaladocIndexer( // Register `@define` macros to use for expanding in later docstrings. defines ++= ScaladocParser.extractDefines(docstring) val comment = ScaladocParser.parseComment(docstring, defines) - val markdown = MarkdownGenerator.toMarkdown(comment, docstring) + val docstringContent = + contentType match { + case MARKDOWN => + MarkdownGenerator.toMarkdown(comment, docstring) + case PLAINTEXT => + PlaintextGenerator.toPlaintext(comment, docstring) + } def param(name: String, default: String): SymbolDocumentation = { val paramDoc = comment.valueParams .get(name) .orElse(comment.typeParams.get(name)) - .map(MarkdownGenerator.toMarkdown) + .map { body => + contentType match { + case MARKDOWN => MarkdownGenerator.toMarkdown(body) + case PLAINTEXT => PlaintextGenerator.toPlaintext(body) + } + } .getOrElse("") MetalsSymbolDocumentation( Symbols.Global(owner, Descriptor.Parameter(name)), @@ -72,7 +87,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, sinfo.displayName, - markdown + docstringContent ) ) case t: Defn.Def => @@ -80,7 +95,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown, + docstringContent, "", t.tparams.map(mparam).asJava, t.paramss.flatten.map(mparam).asJava @@ -91,7 +106,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown, + docstringContent, "", t.tparams.map(mparam).asJava, t.paramss.flatten.map(mparam).asJava @@ -102,7 +117,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown, + docstringContent, "", // Type parameters are intentionally excluded because constructors // cannot have type parameters. @@ -115,7 +130,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown + docstringContent ) ) case _ => @@ -134,9 +149,10 @@ object ScaladocIndexer { */ def foreach( input: Input.VirtualFile, - dialect: Dialect + dialect: Dialect, + contentType: ContentType )(fn: SymbolDocumentation => Unit): Unit = { - new ScaladocIndexer(input, fn, dialect).indexRoot() + new ScaladocIndexer(input, fn, dialect, contentType).indexRoot() } /** diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala new file mode 100644 index 00000000000..678b8fcef37 --- /dev/null +++ b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala @@ -0,0 +1,98 @@ +package scala.meta.internal.docstrings + +import scala.collection.Seq +import scala.util.matching.Regex + +object PlaintextGenerator { + def toPlaintext(b: Body): String = + Option(b).map(body => printBlocks(body.blocks)).mkString("\n") + + def toPlaintext(c: Comment, docstring: String): String = { + + def sortInSection( + section: String, + items: Seq[(String, Body)] + ): Seq[(String, Body)] = { + items.sortBy { case (key, _) => + val reg = new Regex(s"@$section\\s+$key") + reg.findFirstMatchIn(docstring).map(_.start).getOrElse(Int.MaxValue) + } + } + + def printSection(name: String, optBody: Seq[Body]) = + if (optBody.nonEmpty) + optBody + .map(body => s"@$name ${printBlocks(body.blocks)}") + .mkString("\n", "\n", "") + else "" + + def printSortedSection(name: String, section: Seq[(String, Body)]) = + if (section.nonEmpty) + sortInSection(name, section) + .map(tuple => s"@$name ${tuple._1}: " + printBlocks(tuple._2.blocks)) + .mkString("\n", "\n", "") + else "" + + Seq( + toPlaintext(c.body), + printSection("constructor", c.constructor.toSeq), + printSection("deprecated", c.deprecated.toSeq), + printSection("example", c.example), + printSection("notes", c.note), + printSortedSection("tparam", c.typeParams.toSeq), + printSortedSection("param", c.valueParams.toSeq), + printSection("returns", c.result.toSeq), + printSortedSection("throws", c.throws.toSeq), + printSection("see", c.see) + ).reduce(_ + _).trim + } + + private def printBlocks(blocks: Seq[Block]): String = + blocks.map(block => printBlock(block, listLevel = 0)).mkString("\n") + + private def listBlocksIndent( + blocks: Seq[Block], + isOrdered: Boolean, + listLevel: Int + ) = { + var index = 1 + + for (block <- blocks) yield { + val ident = + block match { + case _: OrderedList | _: UnorderedList => "" + case _ => + val bullet = if (isOrdered) index.toString() else "-" + index += 1 + s"""${"\t" * listLevel}${bullet} """ + } + s"${ident}${printBlock(block, listLevel + 1)}" + } + } + + private def printBlock(block: Block, listLevel: Int): String = + block match { + case Title(text, _) => + s"""==${printInline(text)}==""" + case UnorderedList(blocks) => + this.listBlocksIndent(blocks, isOrdered = false, listLevel).mkString + case OrderedList(blocks, _) => + this.listBlocksIndent(blocks, isOrdered = true, listLevel).mkString + case Paragraph(text) => s"${printInline(text)}" + case Code(data) => s"{{{\n$data\n}}}" + case _ => "" + } + + private def printInline(i: Inline): String = + i match { + case Chain(items) => items.map(printInline).mkString + case Summary(text) => printInline(text) + case Monospace(text) => printInline(text) + case Italic(text) => printInline(text) + case Bold(text) => printInline(text) + case Link(_, title) => s"[[${printInline(title)}]]" + case Text(text) => text + case HtmlTag(data) => data + case _ => "" + } +} diff --git a/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala b/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala index 1d9efc3d656..d6cd1f43ceb 100644 --- a/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala +++ b/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala @@ -6,7 +6,7 @@ import scala.meta.XtensionSyntax import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.mtags.CommonMtagsEnrichments.XtensionOptionalJava -import scala.meta.pc.HoverContentType +import scala.meta.pc.ContentType import munit.Location import munit.TestOptions @@ -26,7 +26,7 @@ abstract class BaseHoverSuite includeRange: Boolean = false, automaticPackage: Boolean = true, compat: Map[String, String] = Map.empty, - contentType: HoverContentType = HoverContentType.MARKDOWN + contentType: ContentType = ContentType.MARKDOWN )(implicit loc: Location): Unit = { test(testOpt) { val filename = "Hover.scala" @@ -45,10 +45,10 @@ abstract class BaseHoverSuite CompilerRangeParams(Paths.get(filename).toUri(), code, so, eo) } val hover = presentationCompiler - .hover(pcParams) + .hover(pcParams, contentType) .get() .asScala - .map(_.toLsp(contentType)) + .map(_.toLsp()) val obtained: String = renderAsString(code, hover, includeRange) assertNoDiff( obtained, diff --git a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala index bd6e751a1da..68633d8b221 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala @@ -1,6 +1,6 @@ package tests.hover -import scala.meta.pc.HoverContentType +import scala.meta.pc.ContentType import tests.pc.BaseHoverSuite @@ -548,6 +548,54 @@ class HoverDocSuite extends BaseHoverSuite { |Some docstring | |""".stripMargin, - contentType = HoverContentType.PLAINTEXT + contentType = ContentType.PLAINTEXT + ) + + check( + "fold-plaintext", + """|object a { + | < @@)>> + |} + |""".stripMargin, + """|Expression type: + |String + | + |Symbol signature: + |final def fold[B](ifEmpty: => B)(f: Int => B): B + | + |Returns the result of applying f to this [[scala.Option]]'s + | value if the [[scala.Option]] is nonempty. Otherwise, evaluates + | expression ifEmpty. + |This is equivalent to: + |{{{ + |option match { + | case Some(x) => f(x) + | case None => ifEmpty + |} + |}}} + |This is also equivalent to: + |{{{ + |option map f getOrElse ifEmpty + |}}} + |@param ifEmpty: the expression to evaluate if empty. + |@param f: the function to apply if nonempty. + |""".stripMargin, + contentType = ContentType.PLAINTEXT + ) + + check( + "head-plaintext", + """|object a { + | <> + |} + |""".stripMargin, + """|def head: Int + | + |Selects the first element of this iterable collection. + | Note: might return different results for different runs, unless the underlying collection type is ordered. + |@returns the first element of this iterable collection. + |@throws NoSuchElementException: if the iterable collection is empty. + |""".stripMargin, + contentType = ContentType.PLAINTEXT ) } diff --git a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala index 411b94b595d..7048f5d3370 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala @@ -1,6 +1,6 @@ package tests.hover -import scala.meta.pc.HoverContentType +import scala.meta.pc.ContentType import tests.pc.BaseHoverSuite @@ -729,7 +729,7 @@ class HoverTermSuite extends BaseHoverSuite { compat = Map( "3" -> "trait App: App" ), - contentType = HoverContentType.PLAINTEXT + contentType = ContentType.PLAINTEXT ) check( @@ -751,6 +751,6 @@ class HoverTermSuite extends BaseHoverSuite { |Symbol signature: |def subConsumer[T](i: T): T |""".stripMargin, - contentType = HoverContentType.PLAINTEXT + contentType = ContentType.PLAINTEXT ) } diff --git a/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala b/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala index 65609dbc56b..ebf93869901 100644 --- a/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala @@ -10,6 +10,7 @@ import scala.meta.internal.async.CompletableCancelToken import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.mtags.Symbol import scala.meta.internal.mtags.SymbolDefinition +import scala.meta.pc.ContentType import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompiler @@ -84,7 +85,7 @@ class InterruptPresentationCompilerSuite extends BasePCSuite { |} |""".stripMargin, (pc, params) => { - pc.hover(params) + pc.hover(params, ContentType.MARKDOWN) } ) diff --git a/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala b/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala index 18a0064e991..49e9cb0a93c 100644 --- a/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala +++ b/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala @@ -6,7 +6,7 @@ import java.nio.file.Paths import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.mtags.MtagsEnrichments._ -import scala.meta.pc.HoverContentType +import scala.meta.pc.ContentType import munit.Location import munit.TestOptions @@ -40,13 +40,13 @@ class BaseJavaHoverSuite extends BaseJavaPCSuite with TestHovers { CompilerRangeParams(uri, code, so, eo) } val hover = presentationCompiler - .hover(pcParams) + .hover(pcParams, ContentType.MARKDOWN) .get() val obtained: String = renderAsString( code, - hover.asScala.map(_.toLsp(HoverContentType.MARKDOWN)), + hover.asScala.map(_.toLsp()), includeRange, ) diff --git a/tests/mtest/src/main/scala/tests/TestingSymbolSearch.scala b/tests/mtest/src/main/scala/tests/TestingSymbolSearch.scala index 18ed873818d..cfd563081b0 100644 --- a/tests/mtest/src/main/scala/tests/TestingSymbolSearch.scala +++ b/tests/mtest/src/main/scala/tests/TestingSymbolSearch.scala @@ -15,6 +15,7 @@ import scala.meta.internal.mtags.Mtags import scala.meta.internal.mtags.OnDemandSymbolIndex import scala.meta.internal.mtags.Symbol import scala.meta.internal.{semanticdb => s} +import scala.meta.pc.ContentType import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation import scala.meta.pc.SymbolSearch @@ -35,11 +36,19 @@ class TestingSymbolSearch( index: GlobalSymbolIndex = OnDemandSymbolIndex.empty()(EmptyReportContext) )(implicit rc: ReportContext = EmptyReportContext) extends SymbolSearch { + override def documentation( symbol: String, parents: ParentSymbols + ): Optional[SymbolDocumentation] = + documentation(symbol, parents, ContentType.MARKDOWN) + + override def documentation( + symbol: String, + parents: ParentSymbols, + contentType: ContentType ): Optional[SymbolDocumentation] = { - docs.documentation(symbol, parents) + docs.documentation(symbol, parents, contentType) } override def definition(symbol: String, source: URI): ju.List[Location] = { From 115d6ae37febbe0775ab8ecf0ce45651adbc542c Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 17 Apr 2024 15:44:36 +0200 Subject: [PATCH 04/11] tests adjustments --- .../main/scala/scala/meta/internal/pc/ScalaHover.scala | 5 +++-- .../cross/src/test/scala/tests/hover/HoverDocSuite.scala | 8 +++++--- .../cross/src/test/scala/tests/hover/HoverTermSuite.scala | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala index f5b23e0e3a5..cc3f2367c64 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala @@ -24,7 +24,8 @@ case class ScalaHover( symbolSignature: Option[String], docstring: Option[String], forceExpressionType: Boolean, - range: Option[lsp4j.Range] + range: Option[lsp4j.Range], + contextInfo: List[String] ) = this( expressionType, @@ -32,7 +33,7 @@ case class ScalaHover( docstring, forceExpressionType, range, - contextInfo = Nil, + contextInfo, contentType = ContentType.MARKDOWN ) diff --git a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala index 68633d8b221..9e0cf6820d1 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala @@ -531,7 +531,7 @@ class HoverDocSuite extends BaseHoverSuite { ) check( - "basic-plaintext", + "basic-plaintext".tag(IgnoreForScala3CompilerPC), """| |/** | * Some docstring @@ -552,7 +552,7 @@ class HoverDocSuite extends BaseHoverSuite { ) check( - "fold-plaintext", + "fold-plaintext".tag(IgnoreForScala3CompilerPC), """|object a { | < @@)>> |} @@ -584,7 +584,9 @@ class HoverDocSuite extends BaseHoverSuite { ) check( - "head-plaintext", + "head-plaintext".tag( + IgnoreScala211.and(IgnoreScala212).and(IgnoreForScala3CompilerPC) + ), """|object a { | <> |} diff --git a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala index 7048f5d3370..21488c8c96d 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala @@ -722,7 +722,7 @@ class HoverTermSuite extends BaseHoverSuite { ) check( - "app-plaintext", + "app-plaintext".tag(IgnoreForScala3CompilerPC), """|object Main extends <>{} |""".stripMargin, "abstract trait App: App", @@ -733,7 +733,7 @@ class HoverTermSuite extends BaseHoverSuite { ) check( - "function-chain4-plaintext", + "function-chain4-plaintext".tag(IgnoreForScala3CompilerPC), """ |trait Consumer { | def subConsumer[T](i: T): T From 7e4c5c0d0133a2018ddcc65cab7784ae34979c00 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 18 Apr 2024 13:25:06 +0200 Subject: [PATCH 05/11] set hover content type via pc config --- .../metals/CompilerConfiguration.scala | 1 + .../meta/internal/metals/Compilers.scala | 3 +- .../metals/InlayHintResolveProvider.scala | 1 - .../internal/metals/MetalsLspService.scala | 1 - .../scala/meta/pc/PresentationCompiler.java | 15 +- .../meta/pc/PresentationCompilerConfig.java | 214 +++++++++--------- .../pc/JavaPresentationCompiler.scala | 13 +- .../pc/PresentationCompilerConfigImpl.scala | 4 +- .../pc/ScalaPresentationCompiler.scala | 11 +- .../pc/ScalaPresentationCompiler.scala | 12 +- .../main/scala/tests/pc/BaseHoverSuite.scala | 6 +- .../scala/tests/hover/HoverDocSuite.scala | 73 ------ .../tests/hover/HoverPlaintextSuite.scala | 116 ++++++++++ .../scala/tests/hover/HoverTermSuite.scala | 35 --- .../InterruptPresentationCompilerSuite.scala | 5 +- .../scala/tests/pc/BaseJavaHoverSuite.scala | 5 +- 16 files changed, 251 insertions(+), 264 deletions(-) create mode 100644 tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala diff --git a/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala index bc9b26f7e56..d49cb69d426 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala @@ -239,6 +239,7 @@ class CompilerConfiguration( initializeParams.supportsCompletionSnippets, _isStripMarginOnTypeFormattingEnabled = () => userConfig().enableStripMarginOnTypeFormatting, + hoverContentType = config.hoverContentType(), ) } 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 268a23b67d5..5c315f53c79 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -727,8 +727,7 @@ class Compilers( ): Future[Option[HoverSignature]] = { withPCAndAdjustLsp(params) { (pc, pos, adjust) => pc.hover( - CompilerRangeParamsUtils.offsetOrRange(pos, token), - config.hoverContentType(), + CompilerRangeParamsUtils.offsetOrRange(pos, token) ).asScala .map(_.asScala.map { hover => adjust.adjustHoverResp(hover) }) } diff --git a/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala b/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala index 839ccdb5fc4..0e88eb0b82d 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala @@ -16,7 +16,6 @@ import org.eclipse.{lsp4j => l} final class InlayHintResolveProvider( definitionProvider: DefinitionProvider, compilers: Compilers, - config: ClientConfiguration, )(implicit ec: ExecutionContextExecutorService, rc: ReportContext) { def resolve( inlayHint: InlayHint, diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index 2072d86f519..ef900f8aeff 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -755,7 +755,6 @@ class MetalsLspService( new InlayHintResolveProvider( definitionProvider, compilers, - clientConfig, ) val doctor: Doctor = new Doctor( diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java index 2d4306e6f69..095f681b537 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -80,17 +80,8 @@ public CompletableFuture> semanticTokens(VirtualFileParams params) { * Returns the type of the expression at the given position along with the * symbol of the referenced symbol. */ - @Deprecated public abstract CompletableFuture> hover(OffsetParams params); - /** - * Returns the type of the expression at the given position along with the - * symbol of the referenced symbol. - */ - public CompletableFuture> hover(OffsetParams params, ContentType contentType) { - return hover(params); - } - /** * Checks if the symbol at given position can be renamed using presentation * compiler. Returns Some(range) if symbol is defined inside a method or the @@ -175,14 +166,16 @@ public abstract CompletableFuture> convertToNamedArguments(Offset public abstract CompletableFuture> didChange(VirtualFileParams params); /** - * Returns decorations for missing type adnotations, inferred type parameters, implicit parameters and conversions. + * Returns decorations for missing type adnotations, inferred type parameters, + * implicit parameters and conversions. */ public CompletableFuture> inlayHints(InlayHintsParams params) { return CompletableFuture.completedFuture(Collections.emptyList()); } /** - * Returns decorations for missing type adnotations, inferred type parameters, implicit parameters and conversions. + * Returns decorations for missing type adnotations, inferred type parameters, + * implicit parameters and conversions. */ public CompletableFuture> syntheticDecorations(SyntheticDecorationsParams params) { return CompletableFuture.completedFuture(Collections.emptyList()); diff --git a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompilerConfig.java b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompilerConfig.java index 01f2454d7cd..afcd8bb3616 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompilerConfig.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompilerConfig.java @@ -12,106 +12,118 @@ */ public interface PresentationCompilerConfig { - /** - * Command ID to trigger parameter hints (textDocument/signatureHelp) in the editor. - * - * See https://scalameta.org/metals/docs/integrations/new-editor.html#compileroptionsparameterhintscommand - * for details. - */ - Optional parameterHintsCommand(); - - /** - * Command ID to trigger completions (textDocument/completion) in the editor. - */ - Optional completionCommand(); - - Map symbolPrefixes(); - - static Map defaultSymbolPrefixes() { - HashMap map = new HashMap<>(); - map.put("scala/collection/mutable/", "mutable."); - map.put("java/util/", "ju."); - return map; - } - - /** - * Returns true if user didn't modify symbol prefixes - */ - boolean isDefaultSymbolPrefixes(); - - /** - * What text format to use for rendering `override def` labels for completion items. - */ - OverrideDefFormat overrideDefFormat(); - - enum OverrideDefFormat { - /** Render as "override def". */ - Ascii, - /** Render as "🔼". */ - Unicode - } - - /** - * Returns true if the CompletionItem.detail field should be populated. - */ - boolean isCompletionItemDetailEnabled(); - - /** - * Returns false if the UserConfiguration.enableStripMarginOnTypeFormatting is configured to false. - */ - boolean isStripMarginOnTypeFormattingEnabled(); - - /** - * Returns true if the CompletionItem.documentation field should be populated. - */ - boolean isCompletionItemDocumentationEnabled(); - - /** - * Returns true if the result from textDocument/hover should include docstrings. - */ - boolean isHoverDocumentationEnabled(); - - /** - * True if the client defaults to adding the identation of the reference - * line that the operation started on (relevant for multiline textEdits) - */ - boolean snippetAutoIndent(); - - /** - * Returns true if the SignatureHelp.documentation field should be populated. - */ - boolean isSignatureHelpDocumentationEnabled(); - - /** - * Returns true if completions can contain snippets. - */ - boolean isCompletionSnippetsEnabled(); - - /** - * The maximum delay for requests to respond. - * - * After the given delay, every request to completions/hover/signatureHelp - * is automatically cancelled and the presentation compiler is restarted. - */ - long timeoutDelay(); - - /** - * The unit to use for timeoutDelay. - */ - TimeUnit timeoutUnit(); - - /** - * The SemanticDB compiler options to use for the semanticdbTextDocument method. - * - * The full list of supported options is documented here https://scalameta.org/docs/semanticdb/guide.html#scalac-compiler-plugin - */ - List semanticdbCompilerOptions(); - - static List defaultSemanticdbCompilerOptions() { - return Arrays.asList( - "-P:semanticdb:synthetics:on", - "-P:semanticdb:text:on" - ); - } + /** + * Command ID to trigger parameter hints (textDocument/signatureHelp) in the + * editor. + * + * See + * https://scalameta.org/metals/docs/integrations/new-editor.html#compileroptionsparameterhintscommand + * for details. + */ + Optional parameterHintsCommand(); + + /** + * Command ID to trigger completions (textDocument/completion) in the editor. + */ + Optional completionCommand(); + + Map symbolPrefixes(); + + static Map defaultSymbolPrefixes() { + HashMap map = new HashMap<>(); + map.put("scala/collection/mutable/", "mutable."); + map.put("java/util/", "ju."); + return map; + } + + /** + * Returns true if user didn't modify symbol prefixes + */ + boolean isDefaultSymbolPrefixes(); + + /** + * What text format to use for rendering `override def` labels for completion + * items. + */ + OverrideDefFormat overrideDefFormat(); + + enum OverrideDefFormat { + /** Render as "override def". */ + Ascii, + /** Render as "🔼". */ + Unicode + } + + /** + * Returns true if the CompletionItem.detail field should be + * populated. + */ + boolean isCompletionItemDetailEnabled(); + + /** + * Returns false if the + * UserConfiguration.enableStripMarginOnTypeFormatting is + * configured to false. + */ + boolean isStripMarginOnTypeFormattingEnabled(); + + /** + * Returns true if the CompletionItem.documentation field should be + * populated. + */ + boolean isCompletionItemDocumentationEnabled(); + + /** + * Returns true if the result from textDocument/hover should + * include docstrings. + */ + boolean isHoverDocumentationEnabled(); + + /** + * True if the client defaults to adding the identation of the reference line + * that the operation started on (relevant for multiline textEdits) + */ + boolean snippetAutoIndent(); + + /** + * Returns true if the SignatureHelp.documentation field should be + * populated. + */ + boolean isSignatureHelpDocumentationEnabled(); + + /** + * Returns true if completions can contain snippets. + */ + boolean isCompletionSnippetsEnabled(); + + /** + * The maximum delay for requests to respond. + * + * After the given delay, every request to completions/hover/signatureHelp is + * automatically cancelled and the presentation compiler is restarted. + */ + long timeoutDelay(); + + /** + * The unit to use for timeoutDelay. + */ + TimeUnit timeoutUnit(); + + /** + * The SemanticDB compiler options to use for the + * semanticdbTextDocument method. + * + * The full list of supported options is documented here + * https://scalameta.org/docs/semanticdb/guide.html#scalac-compiler-plugin + */ + List semanticdbCompilerOptions(); + + static List defaultSemanticdbCompilerOptions() { + return Arrays.asList("-P:semanticdb:synthetics:on", "-P:semanticdb:text:on"); + } + + default ContentType hoverContentType() { + return ContentType.MARKDOWN; + } } diff --git a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala index c4cf55e14a4..17239c2a7cc 100644 --- a/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala +++ b/mtags-java/src/main/scala/scala/meta/internal/pc/JavaPresentationCompiler.scala @@ -14,7 +14,6 @@ import scala.concurrent.ExecutionContextExecutor import scala.jdk.CollectionConverters._ import scala.meta.pc.AutoImportsResult -import scala.meta.pc.ContentType import scala.meta.pc.DefinitionResult import scala.meta.pc.HoverSignature import scala.meta.pc.InlayHintsParams @@ -69,20 +68,16 @@ case class JavaPresentationCompiler( CompletableFuture.completedFuture(new SignatureHelp()) override def hover( - params: OffsetParams, - contentType: ContentType + params: OffsetParams ): CompletableFuture[Optional[HoverSignature]] = CompletableFuture.completedFuture( Optional.ofNullable( - new JavaHoverProvider(javaCompiler, params, contentType).hover().orNull + new JavaHoverProvider(javaCompiler, params, config.hoverContentType()) + .hover() + .orNull ) ) - override def hover( - params: OffsetParams - ): CompletableFuture[Optional[HoverSignature]] = - hover(params, ContentType.MARKDOWN) - override def rename( params: OffsetParams, name: String diff --git a/mtags-shared/src/main/scala/scala/meta/internal/pc/PresentationCompilerConfigImpl.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/PresentationCompilerConfigImpl.scala index 9c95810a54a..12db83917cf 100644 --- a/mtags-shared/src/main/scala/scala/meta/internal/pc/PresentationCompilerConfigImpl.scala +++ b/mtags-shared/src/main/scala/scala/meta/internal/pc/PresentationCompilerConfigImpl.scala @@ -5,6 +5,7 @@ import java.util.Optional import java.util.concurrent.TimeUnit import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.pc.ContentType import scala.meta.pc.PresentationCompilerConfig import scala.meta.pc.PresentationCompilerConfig.OverrideDefFormat @@ -26,7 +27,8 @@ case class PresentationCompilerConfigImpl( timeoutDelay: Long = 20, timeoutUnit: TimeUnit = TimeUnit.SECONDS, semanticdbCompilerOptions: util.List[String] = - PresentationCompilerConfig.defaultSemanticdbCompilerOptions() + PresentationCompilerConfig.defaultSemanticdbCompilerOptions(), + override val hoverContentType: ContentType = ContentType.MARKDOWN ) extends PresentationCompilerConfig { override def isStripMarginOnTypeFormattingEnabled(): Boolean = diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 7ef3d54da56..d6417afc858 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -28,7 +28,6 @@ import scala.meta.internal.metals.StdReportContext import scala.meta.internal.mtags.BuildInfo import scala.meta.internal.mtags.MtagsEnrichments._ import scala.meta.pc.AutoImportsResult -import scala.meta.pc.ContentType import scala.meta.pc.DefinitionResult import scala.meta.pc.DisplayableException import scala.meta.pc.HoverSignature @@ -334,19 +333,15 @@ case class ScalaPresentationCompiler( override def hover( params: OffsetParams - ): CompletableFuture[Optional[HoverSignature]] = - hover(params, ContentType.MARKDOWN) - - override def hover( - params: OffsetParams, - contentType: ContentType ): CompletableFuture[Optional[HoverSignature]] = { compilerAccess.withNonInterruptableCompiler(Some(params))( Optional.empty[HoverSignature](), params.token ) { pc => Optional.ofNullable( - new HoverProvider(pc.compiler(), params, contentType).hover().orNull + new HoverProvider(pc.compiler(), params, config.hoverContentType()) + .hover() + .orNull ) } } diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index e59bdb06355..e1fe6415fb4 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -357,21 +357,13 @@ case class ScalaPresentationCompiler( } end selectionRange - def hover( - params: OffsetParams - ): CompletableFuture[Optional[HoverSignature]] = - hover(params, ContentType.MARKDOWN) - - override def hover( - params: OffsetParams, - contentType: ContentType - ): CompletableFuture[ju.Optional[HoverSignature]] = + override def hover(params: OffsetParams): CompletableFuture[ju.Optional[HoverSignature]] = compilerAccess.withNonInterruptableCompiler(Some(params))( ju.Optional.empty[HoverSignature](), params.token, ) { access => val driver = access.compiler() - HoverProvider.hover(params, driver, search, contentType) + HoverProvider.hover(params, driver, search, config.hoverContentType()) } end hover diff --git a/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala b/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala index d6cd1f43ceb..602f4f83f76 100644 --- a/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala +++ b/tests/cross/src/main/scala/tests/pc/BaseHoverSuite.scala @@ -6,7 +6,6 @@ import scala.meta.XtensionSyntax import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.mtags.CommonMtagsEnrichments.XtensionOptionalJava -import scala.meta.pc.ContentType import munit.Location import munit.TestOptions @@ -25,8 +24,7 @@ abstract class BaseHoverSuite expected: String, includeRange: Boolean = false, automaticPackage: Boolean = true, - compat: Map[String, String] = Map.empty, - contentType: ContentType = ContentType.MARKDOWN + compat: Map[String, String] = Map.empty )(implicit loc: Location): Unit = { test(testOpt) { val filename = "Hover.scala" @@ -45,7 +43,7 @@ abstract class BaseHoverSuite CompilerRangeParams(Paths.get(filename).toUri(), code, so, eo) } val hover = presentationCompiler - .hover(pcParams, contentType) + .hover(pcParams) .get() .asScala .map(_.toLsp()) diff --git a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala index 9e0cf6820d1..4964378dbb8 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverDocSuite.scala @@ -1,7 +1,5 @@ package tests.hover -import scala.meta.pc.ContentType - import tests.pc.BaseHoverSuite class HoverDocSuite extends BaseHoverSuite { @@ -529,75 +527,4 @@ class HoverDocSuite extends BaseHoverSuite { |""".stripMargin ) ) - - check( - "basic-plaintext".tag(IgnoreForScala3CompilerPC), - """| - |/** - | * Some docstring - | */ - |case class Alpha(x: Int) { - |} - | - |object Main { - | val x = <> - |} - |""".stripMargin, - """|def apply(x: Int): Alpha - | - |Some docstring - | - |""".stripMargin, - contentType = ContentType.PLAINTEXT - ) - - check( - "fold-plaintext".tag(IgnoreForScala3CompilerPC), - """|object a { - | < @@)>> - |} - |""".stripMargin, - """|Expression type: - |String - | - |Symbol signature: - |final def fold[B](ifEmpty: => B)(f: Int => B): B - | - |Returns the result of applying f to this [[scala.Option]]'s - | value if the [[scala.Option]] is nonempty. Otherwise, evaluates - | expression ifEmpty. - |This is equivalent to: - |{{{ - |option match { - | case Some(x) => f(x) - | case None => ifEmpty - |} - |}}} - |This is also equivalent to: - |{{{ - |option map f getOrElse ifEmpty - |}}} - |@param ifEmpty: the expression to evaluate if empty. - |@param f: the function to apply if nonempty. - |""".stripMargin, - contentType = ContentType.PLAINTEXT - ) - - check( - "head-plaintext".tag( - IgnoreScala211.and(IgnoreScala212).and(IgnoreForScala3CompilerPC) - ), - """|object a { - | <> - |} - |""".stripMargin, - """|def head: Int - | - |Selects the first element of this iterable collection. - | Note: might return different results for different runs, unless the underlying collection type is ordered. - |@returns the first element of this iterable collection. - |@throws NoSuchElementException: if the iterable collection is empty. - |""".stripMargin, - contentType = ContentType.PLAINTEXT - ) } diff --git a/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala new file mode 100644 index 00000000000..e756aedf1eb --- /dev/null +++ b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala @@ -0,0 +1,116 @@ +package tests.hover +import scala.meta.internal.pc.PresentationCompilerConfigImpl +import scala.meta.pc.ContentType +import scala.meta.pc.PresentationCompilerConfig + +import tests.pc.BaseHoverSuite + +class HoverPlaintextSuite extends BaseHoverSuite { + override def ignoreScalaVersion: Option[IgnoreScalaVersion] = + Some(IgnoreForScala3CompilerPC) + + override protected def requiresScalaLibrarySources: Boolean = true + + override protected def config: PresentationCompilerConfig = + PresentationCompilerConfigImpl().copy( + snippetAutoIndent = false, + hoverContentType = ContentType.PLAINTEXT + ) + + check( + "basic-plaintext", + """| + |/** + | * Some docstring + | */ + |case class Alpha(x: Int) { + |} + | + |object Main { + | val x = <> + |} + |""".stripMargin, + """|def apply(x: Int): Alpha + | + |Some docstring + | + |""".stripMargin + ) + + check( + "fold-plaintext", + """|object a { + | < @@)>> + |} + |""".stripMargin, + """|Expression type: + |String + | + |Symbol signature: + |final def fold[B](ifEmpty: => B)(f: Int => B): B + | + |Returns the result of applying f to this [[scala.Option]]'s + | value if the [[scala.Option]] is nonempty. Otherwise, evaluates + | expression ifEmpty. + |This is equivalent to: + |{{{ + |option match { + | case Some(x) => f(x) + | case None => ifEmpty + |} + |}}} + |This is also equivalent to: + |{{{ + |option map f getOrElse ifEmpty + |}}} + |@param ifEmpty: the expression to evaluate if empty. + |@param f: the function to apply if nonempty. + |""".stripMargin + ) + + check( + "head-plaintext".tag( + IgnoreScala211.and(IgnoreScala212) + ), + """|object a { + | <> + |} + |""".stripMargin, + """|def head: Int + | + |Selects the first element of this iterable collection. + | Note: might return different results for different runs, unless the underlying collection type is ordered. + |@returns the first element of this iterable collection. + |@throws NoSuchElementException: if the iterable collection is empty. + |""".stripMargin + ) + + check( + "app-plaintext", + """|class XX + |object Main extends <>{} + |""".stripMargin, + "class XX: XX" + ) + + check( + "function-chain4-plaintext", + """ + |trait Consumer { + | def subConsumer[T](i: T): T + | def consume(value: Int)(n: Int): Unit + |} + | + |object O { + | val consumer: Consumer = ??? + | List(1).foreach(<>.consume(1)) + |} + |""".stripMargin, + """|Expression type: + |Consumer + | + |Symbol signature: + |def subConsumer[T](i: T): T + |""".stripMargin + ) +} diff --git a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala index 21488c8c96d..38f5f11a210 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverTermSuite.scala @@ -1,7 +1,5 @@ package tests.hover -import scala.meta.pc.ContentType - import tests.pc.BaseHoverSuite class HoverTermSuite extends BaseHoverSuite { @@ -720,37 +718,4 @@ class HoverTermSuite extends BaseHoverSuite { |""".stripMargin, "".stripMargin ) - - check( - "app-plaintext".tag(IgnoreForScala3CompilerPC), - """|object Main extends <>{} - |""".stripMargin, - "abstract trait App: App", - compat = Map( - "3" -> "trait App: App" - ), - contentType = ContentType.PLAINTEXT - ) - - check( - "function-chain4-plaintext".tag(IgnoreForScala3CompilerPC), - """ - |trait Consumer { - | def subConsumer[T](i: T): T - | def consume(value: Int)(n: Int): Unit - |} - | - |object O { - | val consumer: Consumer = ??? - | List(1).foreach(<>.consume(1)) - |} - |""".stripMargin, - """|Expression type: - |Consumer - | - |Symbol signature: - |def subConsumer[T](i: T): T - |""".stripMargin, - contentType = ContentType.PLAINTEXT - ) } diff --git a/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala b/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala index ebf93869901..db898186f95 100644 --- a/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala @@ -10,7 +10,6 @@ import scala.meta.internal.async.CompletableCancelToken import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.mtags.Symbol import scala.meta.internal.mtags.SymbolDefinition -import scala.meta.pc.ContentType import scala.meta.pc.OffsetParams import scala.meta.pc.PresentationCompiler @@ -84,9 +83,7 @@ class InterruptPresentationCompilerSuite extends BasePCSuite { | val x = "".stripS@@uffix("") |} |""".stripMargin, - (pc, params) => { - pc.hover(params, ContentType.MARKDOWN) - } + (pc, params) => pc.hover(params) ) check( diff --git a/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala b/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala index 49e9cb0a93c..a10853054ce 100644 --- a/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala +++ b/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala @@ -6,7 +6,6 @@ import java.nio.file.Paths import scala.meta.internal.metals.CompilerOffsetParams import scala.meta.internal.metals.CompilerRangeParams import scala.meta.internal.mtags.MtagsEnrichments._ -import scala.meta.pc.ContentType import munit.Location import munit.TestOptions @@ -39,9 +38,7 @@ class BaseJavaHoverSuite extends BaseJavaPCSuite with TestHovers { } else { CompilerRangeParams(uri, code, so, eo) } - val hover = presentationCompiler - .hover(pcParams, ContentType.MARKDOWN) - .get() + val hover = presentationCompiler.hover(pcParams).get() val obtained: String = renderAsString( From 48d74a23021a2719e9e13e7f422015e2d326567f Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 18 Apr 2024 13:39:33 +0200 Subject: [PATCH 06/11] use designed `Content` class for (String, ContentType) --- .../meta/internal/metals/Docstrings.scala | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala index 6ae6283618d..0d09c96e32b 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala @@ -23,6 +23,8 @@ import scala.meta.io.AbsolutePath import scala.meta.pc.ContentType import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation +import scala.meta.pc.ContentType.MARKDOWN +import scala.meta.pc.ContentType.PLAINTEXT /** * Implementation of the `documentation(symbol: String): Option[SymbolDocumentation]` method in `SymbolSearch`. @@ -30,7 +32,7 @@ import scala.meta.pc.SymbolDocumentation * Handles both javadoc and scaladoc. */ class Docstrings(index: GlobalSymbolIndex) { - val cache = new TrieMap[(String, ContentType), SymbolDocumentation]() + val cache = new TrieMap[Content, SymbolDocumentation]() private val logger = Logger.getLogger(classOf[Docstrings].getName) def documentation( @@ -38,15 +40,16 @@ class Docstrings(index: GlobalSymbolIndex) { parents: ParentSymbols, contentType: ContentType ): Optional[SymbolDocumentation] = { - val result = cache.get((symbol, contentType)) match { + val content = Content.from(symbol, contentType) + val result = cache.get(content) match { case Some(value) => if (value == EmptySymbolDocumentation) None else Some(value) case None => indexSymbol(symbol, contentType) - val result = cache.get((symbol, contentType)) + val result = cache.get(content) if (result.isEmpty) - cache((symbol, contentType)) = EmptySymbolDocumentation + cache(content) = EmptySymbolDocumentation result } /* Fall back to parent javadocs/scaladocs if nothing is specified for the current symbol @@ -80,10 +83,11 @@ class Docstrings(index: GlobalSymbolIndex) { .parents() .asScala .flatMap { s => - if (cache.contains((s, contentType))) cache.get((s, contentType)) + val content = Content.from(s, contentType) + if (cache.contains(content)) cache.get(content) else { indexSymbol(s, contentType) - cache.get((s, contentType)) + cache.get(content) } } .find(_.docstring().nonEmpty) @@ -91,7 +95,7 @@ class Docstrings(index: GlobalSymbolIndex) { docs } { withDocs => val updated = docs.copy(docstring = withDocs.docstring()) - cache((symbol, contentType)) = updated + cache(Content.from(symbol, contentType)) = updated updated } } @@ -118,7 +122,7 @@ class Docstrings(index: GlobalSymbolIndex) { doc: SymbolDocumentation, contentType: ContentType ): Unit = { - cache((doc.symbol(), contentType)) = doc + cache(Content.from(doc.symbol(), contentType)) = doc } private def indexSymbol(symbol: String, contentType: ContentType): Unit = { @@ -162,7 +166,7 @@ class Docstrings(index: GlobalSymbolIndex) { ): Unit = { for { contentType <- ContentType.values() - } cache.remove((occ.symbol, contentType)) + } cache.remove(Content.from(occ.symbol, contentType)) } } @@ -173,3 +177,15 @@ object Docstrings { OnDemandSymbolIndex.empty() ) } + +sealed trait Content extends Any +class Markdown(val text: String) extends AnyVal with Content +class Plain(val text: String) extends AnyVal with Content + +object Content { + def from(text: String, contentType: ContentType) = + contentType match { + case MARKDOWN => new Markdown(text) + case PLAINTEXT => new Plain(text) + } +} From 6b2e1452dc662314b419cf4cc28628fec6621553 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 18 Apr 2024 14:46:09 +0200 Subject: [PATCH 07/11] extract common scala docstring printers part --- .../metals/CompilerConfiguration.scala | 2 +- .../meta/internal/metals/Docstrings.scala | 6 +- .../meta/internal/metals/JavadocIndexer.scala | 2 +- .../internal/metals/ScaladocIndexer.scala | 21 +-- .../metals/docstrings/MarkdownGenerator.scala | 177 ------------------ .../docstrings/PlaintextGenerator.scala | 98 ---------- .../printers/MarkdownGenerator.scala | 86 +++++++++ .../printers/PlaintextGenerator.scala | 43 +++++ .../docstrings/printers/ScalaDocPrinter.scala | 145 ++++++++++++++ .../tests/hover/HoverPlaintextSuite.scala | 7 + .../src/test/scala/tests/JavadocSuite.scala | 4 +- 11 files changed, 296 insertions(+), 295 deletions(-) delete mode 100644 mtags/src/main/scala/scala/meta/internal/metals/docstrings/MarkdownGenerator.scala delete mode 100644 mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala create mode 100644 mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala create mode 100644 mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/PlaintextGenerator.scala create mode 100644 mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala diff --git a/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala index d49cb69d426..84079928bdf 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/CompilerConfiguration.scala @@ -239,7 +239,7 @@ class CompilerConfiguration( initializeParams.supportsCompletionSnippets, _isStripMarginOnTypeFormattingEnabled = () => userConfig().enableStripMarginOnTypeFormatting, - hoverContentType = config.hoverContentType(), + hoverContentType = config.hoverContentType(), ) } diff --git a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala index 0d09c96e32b..7390f5d5913 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala @@ -21,10 +21,10 @@ import scala.meta.internal.semanticdb.SymbolInformation import scala.meta.internal.semanticdb.SymbolOccurrence import scala.meta.io.AbsolutePath import scala.meta.pc.ContentType -import scala.meta.pc.ParentSymbols -import scala.meta.pc.SymbolDocumentation import scala.meta.pc.ContentType.MARKDOWN import scala.meta.pc.ContentType.PLAINTEXT +import scala.meta.pc.ParentSymbols +import scala.meta.pc.SymbolDocumentation /** * Implementation of the `documentation(symbol: String): Option[SymbolDocumentation]` method in `SymbolSearch`. @@ -183,7 +183,7 @@ class Markdown(val text: String) extends AnyVal with Content class Plain(val text: String) extends AnyVal with Content object Content { - def from(text: String, contentType: ContentType) = + def from(text: String, contentType: ContentType): AnyVal with Content = contentType match { case MARKDOWN => new Markdown(text) case PLAINTEXT => new Plain(text) diff --git a/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala b/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala index adef897a473..e15bca9f32d 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/JavadocIndexer.scala @@ -6,7 +6,7 @@ import scala.util.control.NonFatal import scala.meta.inputs.Input import scala.meta.inputs.Position -import scala.meta.internal.docstrings.MarkdownGenerator +import scala.meta.internal.docstrings.printers.MarkdownGenerator import scala.meta.internal.jdk.CollectionConverters._ import scala.meta.internal.mtags.JavaMtags import scala.meta.internal.semanticdb.Scala.Descriptor diff --git a/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala b/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala index 6000837aa3a..0850624e9ca 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/ScaladocIndexer.scala @@ -31,6 +31,12 @@ class ScaladocIndexer( sinfo: SymbolInformation, owner: String ): Unit = { + val printer = + contentType match { + case MARKDOWN => printers.MarkdownGenerator + case PLAINTEXT => printers.PlaintextGenerator + } + val docstring = currentTree.origin match { case ScalametaCompat.ParsedOrigin(start, _) => val leadingDocstring = @@ -47,23 +53,12 @@ class ScaladocIndexer( // Register `@define` macros to use for expanding in later docstrings. defines ++= ScaladocParser.extractDefines(docstring) val comment = ScaladocParser.parseComment(docstring, defines) - val docstringContent = - contentType match { - case MARKDOWN => - MarkdownGenerator.toMarkdown(comment, docstring) - case PLAINTEXT => - PlaintextGenerator.toPlaintext(comment, docstring) - } + val docstringContent = printer.toText(comment, docstring) def param(name: String, default: String): SymbolDocumentation = { val paramDoc = comment.valueParams .get(name) .orElse(comment.typeParams.get(name)) - .map { body => - contentType match { - case MARKDOWN => MarkdownGenerator.toMarkdown(body) - case PLAINTEXT => PlaintextGenerator.toPlaintext(body) - } - } + .map(printer.toText) .getOrElse("") MetalsSymbolDocumentation( Symbols.Global(owner, Descriptor.Parameter(name)), diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/MarkdownGenerator.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/MarkdownGenerator.scala deleted file mode 100644 index 0d3ba4cbdc1..00000000000 --- a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/MarkdownGenerator.scala +++ /dev/null @@ -1,177 +0,0 @@ -package scala.meta.internal.docstrings - -import scala.collection.Seq -import scala.util.matching.Regex - -import scala.meta._ -import scala.meta.dialects.Scala213 - -/** - * Generates markdown from the docstring - * The markdown can be returned to the IDE and rendered to the user - */ -object MarkdownGenerator { - - /** - * Generates markdown from code - * - * @param code the input code - * @return a sequence of markdown strings - one per docstring comment in the code - */ - def toMarkdown(code: String): Seq[String] = - Scala213(code).tokenize.get.collect { - case c: Token.Comment if c.syntax.startsWith("/**") => - fromDocstring(c.syntax, Map.empty) - } - - def fromDocstring( - docstring: String, - defines: collection.Map[String, String] - ): String = { - toMarkdown(ScaladocParser.parseComment(docstring, defines), docstring) - } - - def toMarkdown(b: Body): String = { - Option(b) - .map(body => blocksToMarkdown(body.blocks)) - .mkString - } - - def toMarkdown(c: Comment, docstring: String): String = { - - def sortInSection( - section: String, - items: Seq[(String, Body)] - ): Seq[(String, Body)] = { - items.sortBy { case (key, _) => - val reg = new Regex(s"@$section\\s+$key") - reg.findFirstMatchIn(docstring).map(_.start).getOrElse(Int.MaxValue) - } - } - - Seq( - toMarkdown(c.body), - if (c.constructor.nonEmpty) - "\n" + c.constructor - .map(body => "**Constructor:** " + blocksToMarkdown(body.blocks)) - .mkString("\n") - else "", - if (c.deprecated.nonEmpty) - "\n" + c.deprecated - .map(body => "**Deprecated:** " ++ blocksToMarkdown(body.blocks)) - .mkString("\n") - else "", - if (c.example.nonEmpty) - "\n**Examples**\n" + c.example - .map(body => blocksToMarkdown(body.blocks)) - .mkString("\n", "\n", "") - else "", - if (c.note.nonEmpty) - "\n**Notes**\n" + - c.note - .map(body => "- " + blocksToMarkdown(body.blocks)) - .mkString("\n") - else "", - if (c.typeParams.nonEmpty) - "\n**Type Parameters**\n" + - sortInSection("tparam", c.typeParams.toSeq) - .map(tuple => - s"- `${tuple._1}`: " + blocksToMarkdown(tuple._2.blocks) - ) - .mkString - else - "", - if (c.valueParams.nonEmpty) - "\n**Parameters**\n" + sortInSection("param", c.valueParams.toSeq) - .map(tuple => - s"- `${tuple._1}`: " + blocksToMarkdown(tuple._2.blocks) - ) - .mkString - else - "", - if (c.result.nonEmpty) - "\n" + c.result - .map(body => "**Returns:** " ++ blocksToMarkdown(body.blocks)) - .mkString("\n") - else "", - if (c.throws.nonEmpty) - "\n**Throws**\n" + sortInSection("throws", c.throws.toSeq) - .map(tuple => - s"- `${tuple._1}`: " + tuple._2.summary - .map(inlineToMarkdown) - .getOrElse("") - ) - .mkString("", "\n", "\n") - else "", - if (c.see.nonEmpty) - "\n**See**\n" + c.see - .map { body => s"- ${blocksToMarkdown(body.blocks).trim}" } - .mkString("", "\n", "\n") - else "" - ).reduce(_ + _).trim - } - - private def blocksToMarkdown(blocks: Seq[Block], listLevel: Int = 0): String = - blocks.map(block => blockToMarkdown(block, listLevel)).mkString("\n") - - private def listBlockIndent(b: Block, bullet: Char, listLevel: Int): String = - b match { - case _: OrderedList | _: UnorderedList => - "" - case _ => - s"""${"\t" * listLevel}${bullet} """ - } - - private def listBlocksIndent( - blocks: Seq[Block], - bullet: Char, - listLevel: Int - ): String = - blocks - .map((b: Block) => - s"${this.listBlockIndent(b, bullet, listLevel)}${this - .blockToMarkdown(b, listLevel + 1)}" - ) - .mkString - - private def blockToMarkdown(block: Block, listLevel: Int): String = - block match { - case Title(text, level) => - s"""${"#" * level} ${inlineToMarkdown(text)}""" - case UnorderedList(blocks) => - this.listBlocksIndent(blocks, '-', listLevel) - case OrderedList(blocks, _) => - this.listBlocksIndent(blocks, '*', listLevel) - case Paragraph(text) => - s"${inlineToMarkdown(text)}\n" - case Code(data) => - s"```\n$data\n```" - case HorizontalRule() => - "---" - case _ => - "" - } - - private def inlineToMarkdown(i: Inline): String = { - i match { - case Chain(items) => - items.map(inlineToMarkdown).mkString - case Summary(text) => - inlineToMarkdown(text) - case Monospace(text) => - s"`${inlineToMarkdown(text)}`" - case Text(text) => - text - case HtmlTag(data) => - data - case Italic(text) => - s"*${inlineToMarkdown(text)}*" - case Bold(text) => - s"**${inlineToMarkdown(text)}**" - case Link(target, title) => - s"[${inlineToMarkdown(title)}]($target)" - case _ => - "" - } - } -} diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala deleted file mode 100644 index 678b8fcef37..00000000000 --- a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/PlaintextGenerator.scala +++ /dev/null @@ -1,98 +0,0 @@ -package scala.meta.internal.docstrings - -import scala.collection.Seq -import scala.util.matching.Regex - -object PlaintextGenerator { - def toPlaintext(b: Body): String = - Option(b).map(body => printBlocks(body.blocks)).mkString("\n") - - def toPlaintext(c: Comment, docstring: String): String = { - - def sortInSection( - section: String, - items: Seq[(String, Body)] - ): Seq[(String, Body)] = { - items.sortBy { case (key, _) => - val reg = new Regex(s"@$section\\s+$key") - reg.findFirstMatchIn(docstring).map(_.start).getOrElse(Int.MaxValue) - } - } - - def printSection(name: String, optBody: Seq[Body]) = - if (optBody.nonEmpty) - optBody - .map(body => s"@$name ${printBlocks(body.blocks)}") - .mkString("\n", "\n", "") - else "" - - def printSortedSection(name: String, section: Seq[(String, Body)]) = - if (section.nonEmpty) - sortInSection(name, section) - .map(tuple => s"@$name ${tuple._1}: " + printBlocks(tuple._2.blocks)) - .mkString("\n", "\n", "") - else "" - - Seq( - toPlaintext(c.body), - printSection("constructor", c.constructor.toSeq), - printSection("deprecated", c.deprecated.toSeq), - printSection("example", c.example), - printSection("notes", c.note), - printSortedSection("tparam", c.typeParams.toSeq), - printSortedSection("param", c.valueParams.toSeq), - printSection("returns", c.result.toSeq), - printSortedSection("throws", c.throws.toSeq), - printSection("see", c.see) - ).reduce(_ + _).trim - } - - private def printBlocks(blocks: Seq[Block]): String = - blocks.map(block => printBlock(block, listLevel = 0)).mkString("\n") - - private def listBlocksIndent( - blocks: Seq[Block], - isOrdered: Boolean, - listLevel: Int - ) = { - var index = 1 - - for (block <- blocks) yield { - val ident = - block match { - case _: OrderedList | _: UnorderedList => "" - case _ => - val bullet = if (isOrdered) index.toString() else "-" - index += 1 - s"""${"\t" * listLevel}${bullet} """ - } - s"${ident}${printBlock(block, listLevel + 1)}" - } - } - - private def printBlock(block: Block, listLevel: Int): String = - block match { - case Title(text, _) => - s"""==${printInline(text)}==""" - case UnorderedList(blocks) => - this.listBlocksIndent(blocks, isOrdered = false, listLevel).mkString - case OrderedList(blocks, _) => - this.listBlocksIndent(blocks, isOrdered = true, listLevel).mkString - case Paragraph(text) => s"${printInline(text)}" - case Code(data) => s"{{{\n$data\n}}}" - case _ => "" - } - - private def printInline(i: Inline): String = - i match { - case Chain(items) => items.map(printInline).mkString - case Summary(text) => printInline(text) - case Monospace(text) => printInline(text) - case Italic(text) => printInline(text) - case Bold(text) => printInline(text) - case Link(_, title) => s"[[${printInline(title)}]]" - case Text(text) => text - case HtmlTag(data) => data - case _ => "" - } -} diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala new file mode 100644 index 00000000000..60bff49e9cb --- /dev/null +++ b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala @@ -0,0 +1,86 @@ +package scala.meta.internal.docstrings.printers + +import scala.collection.Seq + +import scala.meta._ +import scala.meta.dialects.Scala213 +import scala.meta.internal.docstrings._ + +/** + * Generates markdown from the docstring + * The markdown can be returned to the IDE and rendered to the user + */ +object MarkdownGenerator extends ScalaDocPrinter { + + /** + * Generates markdown from code + * + * @param code the input code + * @return a sequence of markdown strings - one per docstring comment in the code + */ + def toText(code: String): Seq[String] = + Scala213(code).tokenize.get.collect { + case c: Token.Comment if c.syntax.startsWith("/**") => + fromDocstring(c.syntax, Map.empty) + } + + def fromDocstring( + docstring: String, + defines: collection.Map[String, String] + ): String = { + super.toText(ScaladocParser.parseComment(docstring, defines), docstring) + } + + protected def blockToText(block: Block, listLevel: Int): String = + block match { + case Title(text, level) => + s"""${"#" * level} ${inlineToText(text)}""" + case UnorderedList(blocks) => + listBlocksIndent(blocks, ListType.Bullet('-'), listLevel) + case OrderedList(blocks, _) => + listBlocksIndent(blocks, ListType.Bullet('*'), listLevel) + case Paragraph(text) => + s"${inlineToText(text)}\n" + case Code(data) => + s"```\n$data\n```" + case HorizontalRule() => + "---" + case _ => + "" + } + + protected def inlineToText(i: Inline): String = { + i match { + case Chain(items) => + items.map(inlineToText).mkString + case Summary(text) => + inlineToText(text) + case Monospace(text) => + s"`${inlineToText(text)}`" + case Text(text) => + text + case HtmlTag(data) => + data + case Italic(text) => + s"*${inlineToText(text)}*" + case Bold(text) => + s"**${inlineToText(text)}**" + case Link(target, title) => + s"[${inlineToText(title)}]($target)" + case _ => + "" + } + } + + protected def wrapParam(param: String): String = s"`$param`" + protected def constructor: String = "**Constructor**" + protected def deprecated: String = "**Deprecated**" + protected def examples: String = "**Examples**\n" + override protected def notesTitle: String = "**Notes**\n" + override protected def typeParamsTitle: String = "**Type Parameters**\n" + override protected def parametersTitle: String = "**Parameters**\n" + protected def returns: String = "**Returns:**" + override protected def throwsTitle: String = "**Throws**\n" + override protected def seeTitle: String = "**See**\n" + +} diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/PlaintextGenerator.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/PlaintextGenerator.scala new file mode 100644 index 00000000000..5476a426301 --- /dev/null +++ b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/PlaintextGenerator.scala @@ -0,0 +1,43 @@ +package scala.meta.internal.docstrings.printers + +import scala.meta.internal.docstrings._ + +object PlaintextGenerator extends ScalaDocPrinter { + + def blockToText(block: Block, listLevel: Int): String = + block match { + case Title(text, _) => + s"""==${inlineToText(text)}==""" + case UnorderedList(blocks) => + listBlocksIndent(blocks, ListType.Bullet('-'), listLevel) + case OrderedList(blocks, _) => + listBlocksIndent(blocks, ListType.Ordered, listLevel) + case Paragraph(text) => s"${inlineToText(text)}\n" + case Code(data) => s"{{{\n$data\n}}}\n" + case _ => "" + } + + def inlineToText(i: Inline): String = + i match { + case Chain(items) => items.map(inlineToText).mkString + case Summary(text) => inlineToText(text) + case Monospace(text) => inlineToText(text) + case Italic(text) => inlineToText(text) + case Bold(text) => inlineToText(text) + case Link(_, title) => s"[[${inlineToText(title)}]]" + case Text(text) => text + case HtmlTag(data) => data + case _ => "" + } + + protected def wrapParam(param: String): String = param + protected def constructor: String = "@constructor" + protected def deprecated: String = "@deprecated" + protected def examples: String = "@example" + override protected def note: String = "@note" + override protected def typeParam: String = "@tparam" + override protected def param: String = "@param" + protected def returns: String = "@returns" + override protected def throws: String = "@throws" + override protected def see: String = "@see" +} diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala new file mode 100644 index 00000000000..205fb17bd77 --- /dev/null +++ b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala @@ -0,0 +1,145 @@ +package scala.meta.internal.docstrings.printers + +import scala.collection.Seq +import scala.util.matching.Regex + +import scala.meta.internal.docstrings._ + +abstract class ScalaDocPrinter { + def toText(b: Body): String = { + Option(b) + .map(body => blocksToText(body.blocks)) + .mkString + } + + def toText(c: Comment, docstring: String): String = { + + def sortInSection( + section: String, + items: Seq[(String, Body)] + ): Seq[(String, Body)] = { + items.sortBy { case (key, _) => + val reg = new Regex(s"@$section\\s+$key") + reg.findFirstMatchIn(docstring).map(_.start).getOrElse(Int.MaxValue) + } + } + + Seq( + toText(c.body), + if (c.constructor.nonEmpty) + "\n" + c.constructor + .map(body => constructor + blocksToText(body.blocks)) + .mkString("\n") + else "", + if (c.deprecated.nonEmpty) + "\n" + c.deprecated + .map(body => deprecated ++ blocksToText(body.blocks)) + .mkString("\n") + else "", + if (c.example.nonEmpty) + s"\n$examples" + c.example + .map(body => blocksToText(body.blocks)) + .mkString("\n", "\n", "") + else "", + if (c.note.nonEmpty) + s"\n$notesTitle" + + c.note + .map(body => s"$note " + blocksToText(body.blocks)) + .mkString("\n") + else "", + if (c.typeParams.nonEmpty) + s"\n$typeParamsTitle" + + sortInSection("tparam", c.typeParams.toSeq) + .map(tuple => + s"$typeParam ${wrapParam(tuple._1)}: " + blocksToText( + tuple._2.blocks + ) + ) + .mkString + else + "", + if (c.valueParams.nonEmpty) + s"\n$parametersTitle" + sortInSection("param", c.valueParams.toSeq) + .map(tuple => + s"$param ${wrapParam(tuple._1)}: " + blocksToText(tuple._2.blocks) + ) + .mkString + else + "", + if (c.result.nonEmpty) + "\n" + c.result + .map(body => returns ++ " " ++ blocksToText(body.blocks)) + .mkString("\n") + else "", + if (c.throws.nonEmpty) + s"\n$throwsTitle" + sortInSection("throws", c.throws.toSeq) + .map(tuple => + s"$throws ${wrapParam(tuple._1)}: " + tuple._2.summary + .map(inlineToText) + .getOrElse("") + ) + .mkString("", "\n", "\n") + else "", + if (c.see.nonEmpty) + s"\n$seeTitle" + c.see + .map { body => s"$see ${blocksToText(body.blocks).trim}" } + .mkString("", "\n", "\n") + else "" + ).reduce(_ + _).trim + } + + protected def blocksToText(blocks: Seq[Block], listLevel: Int = 0): String = + blocks.map(block => blockToText(block, listLevel)).mkString("\n") + + protected def listBlocksIndent( + blocks: Seq[Block], + listType: ListType, + listLevel: Int + ): String = { + var index = 1 + + (for (block <- blocks) yield { + val ident = + block match { + case _: OrderedList | _: UnorderedList => "" + case _ => + val bullet = listType.getBullet(index) + index += 1 + s"""${"\t" * listLevel}${bullet} """ + } + s"${ident}${blockToText(block, listLevel + 1)}" + }).mkString + } + + protected def blockToText(block: Block, listLevel: Int): String + protected def inlineToText(i: Inline): String + protected def wrapParam(param: String): String + protected def constructor: String + protected def deprecated: String + protected def examples: String + protected def notesTitle: String = "" + protected def note: String = "-" + protected def typeParamsTitle: String = "" + protected def typeParam: String = "-" + protected def parametersTitle: String = "" + protected def param: String = "-" + protected def returns: String + protected def throwsTitle: String = "" + protected def throws: String = "-" + protected def seeTitle: String = "" + protected def see: String = "-" + +} + +trait ListType { + def getBullet(i: Int): String +} +object ListType { + case object Ordered extends ListType { + def getBullet(i: Int): String = i.toString() + } + + case class Bullet(c: Char) extends ListType { + def getBullet(i: Int): String = c.toString() + } +} diff --git a/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala index e756aedf1eb..b3e32ab67e7 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala @@ -52,17 +52,22 @@ class HoverPlaintextSuite extends BaseHoverSuite { |Returns the result of applying f to this [[scala.Option]]'s | value if the [[scala.Option]] is nonempty. Otherwise, evaluates | expression ifEmpty. + | |This is equivalent to: + | |{{{ |option match { | case Some(x) => f(x) | case None => ifEmpty |} |}}} + | |This is also equivalent to: + | |{{{ |option map f getOrElse ifEmpty |}}} + | |@param ifEmpty: the expression to evaluate if empty. |@param f: the function to apply if nonempty. |""".stripMargin @@ -80,7 +85,9 @@ class HoverPlaintextSuite extends BaseHoverSuite { | |Selects the first element of this iterable collection. | Note: might return different results for different runs, unless the underlying collection type is ordered. + | |@returns the first element of this iterable collection. + | |@throws NoSuchElementException: if the iterable collection is empty. |""".stripMargin ) diff --git a/tests/unit/src/test/scala/tests/JavadocSuite.scala b/tests/unit/src/test/scala/tests/JavadocSuite.scala index f28682f3301..b0ba87445d4 100644 --- a/tests/unit/src/test/scala/tests/JavadocSuite.scala +++ b/tests/unit/src/test/scala/tests/JavadocSuite.scala @@ -1,6 +1,6 @@ package tests -import scala.meta.internal.docstrings.MarkdownGenerator +import scala.meta.internal.docstrings.printers.MarkdownGenerator import munit.Location @@ -10,7 +10,7 @@ class JavadocSuite extends BaseSuite { loc: Location ): Unit = { test(name) { - val obtained = MarkdownGenerator.toMarkdown(original).mkString + val obtained = MarkdownGenerator.toText(original).mkString assertNoDiff(obtained, expected) } } From c4ad20a96cc61430da978e6ee5824cabc9b1aac9 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 19 Apr 2024 13:35:21 +0200 Subject: [PATCH 08/11] test fix --- .../src/test/scala/tests/hover/HoverPlaintextSuite.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala index b3e32ab67e7..9864c4a4523 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala @@ -93,11 +93,12 @@ class HoverPlaintextSuite extends BaseHoverSuite { ) check( - "app-plaintext", - """|class XX + "trait-plaintext", + """|trait XX |object Main extends <>{} |""".stripMargin, - "class XX: XX" + "abstract trait XX: XX", + compat = Map("3" -> "trait XX: XX") ) check( From 83757af5d159f8eb1d49d0df08e067303a70b524 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 19 Apr 2024 17:04:59 +0200 Subject: [PATCH 09/11] fix type --- .../src/main/scala/scala/meta/internal/metals/Docstrings.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala index 7390f5d5913..434a511c1d6 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/Docstrings.scala @@ -183,7 +183,7 @@ class Markdown(val text: String) extends AnyVal with Content class Plain(val text: String) extends AnyVal with Content object Content { - def from(text: String, contentType: ContentType): AnyVal with Content = + def from(text: String, contentType: ContentType): Content = contentType match { case MARKDOWN => new Markdown(text) case PLAINTEXT => new Plain(text) From 6f67b0470b9cda0da113636434063eebf3e7960f Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 22 Apr 2024 10:54:03 +0200 Subject: [PATCH 10/11] formatting fix --- .../metals/docstrings/printers/MarkdownGenerator.scala | 4 ++-- .../internal/metals/docstrings/printers/ScalaDocPrinter.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala index 60bff49e9cb..6bfead0ad66 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala @@ -73,8 +73,8 @@ object MarkdownGenerator extends ScalaDocPrinter { } protected def wrapParam(param: String): String = s"`$param`" - protected def constructor: String = "**Constructor**" - protected def deprecated: String = "**Deprecated**" + protected def constructor: String = "**Constructor:**" + protected def deprecated: String = "**Deprecated:**" protected def examples: String = "**Examples**\n" override protected def notesTitle: String = "**Notes**\n" override protected def typeParamsTitle: String = "**Type Parameters**\n" diff --git a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala index 205fb17bd77..d73a60188c7 100644 --- a/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala +++ b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/ScalaDocPrinter.scala @@ -28,12 +28,12 @@ abstract class ScalaDocPrinter { toText(c.body), if (c.constructor.nonEmpty) "\n" + c.constructor - .map(body => constructor + blocksToText(body.blocks)) + .map(body => constructor + " " + blocksToText(body.blocks)) .mkString("\n") else "", if (c.deprecated.nonEmpty) "\n" + c.deprecated - .map(body => deprecated ++ blocksToText(body.blocks)) + .map(body => deprecated ++ " " ++ blocksToText(body.blocks)) .mkString("\n") else "", if (c.example.nonEmpty) From 64ae700428bc9404d57b96efa95de5bfc687e364 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 22 Apr 2024 11:35:15 +0200 Subject: [PATCH 11/11] rebase fix --- .../cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala index 9864c4a4523..3d8c93e662e 100644 --- a/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala +++ b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala @@ -6,8 +6,6 @@ import scala.meta.pc.PresentationCompilerConfig import tests.pc.BaseHoverSuite class HoverPlaintextSuite extends BaseHoverSuite { - override def ignoreScalaVersion: Option[IgnoreScalaVersion] = - Some(IgnoreForScala3CompilerPC) override protected def requiresScalaLibrarySources: Boolean = true