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 537f41843b2..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,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.ContentType import org.eclipse.lsp4j.ClientCapabilities import org.eclipse.lsp4j.InitializeParams @@ -161,6 +162,18 @@ final class ClientConfiguration( } yield true }.getOrElse(false) + def hoverContentType(): ContentType = + (for { + capabilities <- clientCapabilities + textDocumentCapabilities <- Option(capabilities.getTextDocument()) + hoverCapabilities <- Option(textDocumentCapabilities.getHover()) + contentTypes <- Option(hoverCapabilities.getContentFormat()) + } yield { + if (contentTypes.contains(ContentType.MARKDOWN.toString())) + ContentType.MARKDOWN + else ContentType.PLAINTEXT + }).getOrElse(ContentType.MARKDOWN) + def isTreeViewProvider(): Boolean = extract( initializationOptions.treeViewProvider, 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..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,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 4a25ff48c77..5c315f53c79 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,9 @@ 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) + ).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 1803d1e1133..0e88eb0b82d 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/InlayHintResolveProvider.scala @@ -69,7 +69,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().getContents().getRight() + ) + ) labelPart } } 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/ContentType.java b/mtags-interfaces/src/main/java/scala/meta/pc/ContentType.java new file mode 100644 index 00000000000..99e5b8c0f90 --- /dev/null +++ b/mtags-interfaces/src/main/java/scala/meta/pc/ContentType.java @@ -0,0 +1,16 @@ +package scala.meta.pc; + +public enum ContentType { + MARKDOWN("markdown"), + PLAINTEXT("plaintext"); + + private final String name; + ContentType(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..f8762d086ee 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/HoverSignature.java @@ -10,4 +10,7 @@ public interface HoverSignature { Optional signature(); Optional getRange(); HoverSignature withRange(Range range); + 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..095f681b537 100644 --- a/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java +++ b/mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompiler.java @@ -166,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-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 c472eb2f788..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,6 +3,8 @@ package scala.meta.internal.pc import java.util.Optional import scala.meta.internal.mtags.CommonMtagsEnrichments._ +import scala.meta.pc.ContentType +import scala.meta.pc.ContentType.MARKDOWN import scala.meta.pc.HoverSignature import org.eclipse.lsp4j @@ -12,20 +14,21 @@ 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 = { - val markdown = - HoverMarkup.javaHoverMarkup( - expressionType.getOrElse(""), - symbolSignature.getOrElse(""), - docstring.getOrElse(""), - forceExpressionType - ) - new lsp4j.Hover(markdown.toMarkupContent, range.orNull) + override def toLsp(): 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-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..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 @@ -72,7 +72,9 @@ case class JavaPresentationCompiler( ): CompletableFuture[Optional[HoverSignature]] = CompletableFuture.completedFuture( Optional.ofNullable( - new JavaHoverProvider(javaCompiler, params).hover().orNull + new JavaHoverProvider(javaCompiler, params, config.hoverContentType()) + .hover() + .orNull ) ) 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..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,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.ContentType import scala.meta.pc.OffsetParams import scala.meta.pc.RangeParams import scala.meta.pc.VirtualFileParams @@ -275,9 +276,11 @@ trait CommonMtagsEnrichments { start >= 0 && doc.startsWith(value, start) } - def toMarkupContent: l.MarkupContent = { + def toMarkupContent( + contentType: ContentType = ContentType.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/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/HoverMarkup.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/HoverMarkup.scala index 9a7e2add956..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 @@ -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( + if (optSymbolSignature.isDefined) Some("Expression type") else None, + 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( + if (forceExpressionType) Some("Symbol signature") else None, + 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( + if (forceExpressionType) Some("Symbol signature") else None, + 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/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-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala b/mtags-shared/src/main/scala/scala/meta/internal/pc/ScalaHover.scala index da79351dd61..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 @@ -3,6 +3,8 @@ package scala.meta.internal.pc import java.util.Optional import scala.meta.internal.mtags.CommonMtagsEnrichments._ +import scala.meta.pc.ContentType +import scala.meta.pc.ContentType.MARKDOWN import scala.meta.pc.HoverSignature import org.eclipse.lsp4j @@ -13,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( @@ -21,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, @@ -29,21 +33,23 @@ case class ScalaHover( docstring, forceExpressionType, range, - contextInfo = Nil + contextInfo, + contentType = ContentType.MARKDOWN ) def signature(): Optional[String] = symbolSignature.asJava def toLsp(): lsp4j.Hover = { - val markdown = + 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 @@ -55,7 +61,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..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 @@ -333,15 +333,18 @@ case class ScalaPresentationCompiler( override def hover( params: OffsetParams - ): CompletableFuture[Optional[HoverSignature]] = + ): 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, config.hoverContentType()) + .hover() + .orNull ) } + } def definition(params: OffsetParams): CompletableFuture[DefinitionResult] = { compilerAccess.withNonInterruptableCompiler(Some(params))( 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/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..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,15 +357,13 @@ case class ScalaPresentationCompiler( } end selectionRange - def hover( - params: OffsetParams - ): 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) + HoverProvider.hover(params, driver, search, config.hoverContentType()) } 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..434a511c1d6 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,9 @@ 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.ContentType.MARKDOWN +import scala.meta.pc.ContentType.PLAINTEXT import scala.meta.pc.ParentSymbols import scala.meta.pc.SymbolDocumentation @@ -29,22 +32,24 @@ import scala.meta.pc.SymbolDocumentation * Handles both javadoc and scaladoc. */ class Docstrings(index: GlobalSymbolIndex) { - val cache = new TrieMap[String, SymbolDocumentation]() + val cache = new TrieMap[Content, 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 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) - val result = cache.get(symbol) + indexSymbol(symbol, contentType) + val result = cache.get(content) if (result.isEmpty) - cache(symbol) = EmptySymbolDocumentation + cache(content) = EmptySymbolDocumentation result } /* Fall back to parent javadocs/scaladocs if nothing is specified for the current symbol @@ -53,13 +58,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 +76,18 @@ 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) + val content = Content.from(s, contentType) + if (cache.contains(content)) cache.get(content) else { - indexSymbol(s) - cache.get(s) + indexSymbol(s, contentType) + cache.get(content) } } .find(_.docstring().nonEmpty) @@ -87,7 +95,7 @@ class Docstrings(index: GlobalSymbolIndex) { docs } { withDocs => val updated = docs.copy(docstring = withDocs.docstring()) - cache(symbol) = updated + cache(Content.from(symbol, contentType)) = updated updated } } @@ -110,15 +118,18 @@ class Docstrings(index: GlobalSymbolIndex) { } } - private def cacheSymbol(doc: SymbolDocumentation): Unit = { - cache(doc.symbol()) = doc + private def cacheSymbol( + doc: SymbolDocumentation, + contentType: ContentType + ): Unit = { + cache(Content.from(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 +138,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 +164,9 @@ class Docstrings(index: GlobalSymbolIndex) { sinfo: SymbolInformation, owner: String ): Unit = { - cache.remove(occ.symbol) + for { + contentType <- ContentType.values() + } cache.remove(Content.from(occ.symbol, contentType)) } } @@ -159,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): 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 50cf8403135..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,12 +6,15 @@ 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 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..0850624e9ca 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( @@ -27,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 = @@ -43,12 +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 markdown = MarkdownGenerator.toMarkdown(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(MarkdownGenerator.toMarkdown) + .map(printer.toText) .getOrElse("") MetalsSymbolDocumentation( Symbols.Global(owner, Descriptor.Parameter(name)), @@ -72,7 +82,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, sinfo.displayName, - markdown + docstringContent ) ) case t: Defn.Def => @@ -80,7 +90,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown, + docstringContent, "", t.tparams.map(mparam).asJava, t.paramss.flatten.map(mparam).asJava @@ -91,7 +101,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown, + docstringContent, "", t.tparams.map(mparam).asJava, t.paramss.flatten.map(mparam).asJava @@ -102,7 +112,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown, + docstringContent, "", // Type parameters are intentionally excluded because constructors // cannot have type parameters. @@ -115,7 +125,7 @@ class ScaladocIndexer( MetalsSymbolDocumentation( occ.symbol, t.name.value, - markdown + docstringContent ) ) case _ => @@ -134,9 +144,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/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/printers/MarkdownGenerator.scala b/mtags/src/main/scala/scala/meta/internal/metals/docstrings/printers/MarkdownGenerator.scala new file mode 100644 index 00000000000..6bfead0ad66 --- /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..d73a60188c7 --- /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 new file mode 100644 index 00000000000..3d8c93e662e --- /dev/null +++ b/tests/cross/src/test/scala/tests/hover/HoverPlaintextSuite.scala @@ -0,0 +1,122 @@ +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 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( + "trait-plaintext", + """|trait XX + |object Main extends <>{} + |""".stripMargin, + "abstract trait XX: XX", + compat = Map("3" -> "trait 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/pc/InterruptPresentationCompilerSuite.scala b/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala index 65609dbc56b..db898186f95 100644 --- a/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala +++ b/tests/cross/src/test/scala/tests/pc/InterruptPresentationCompilerSuite.scala @@ -83,9 +83,7 @@ class InterruptPresentationCompilerSuite extends BasePCSuite { | val x = "".stripS@@uffix("") |} |""".stripMargin, - (pc, params) => { - pc.hover(params) - } + (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 c0c031d99c3..a10853054ce 100644 --- a/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala +++ b/tests/javapc/src/main/scala/tests/pc/BaseJavaHoverSuite.scala @@ -38,12 +38,14 @@ class BaseJavaHoverSuite extends BaseJavaPCSuite with TestHovers { } else { CompilerRangeParams(uri, code, so, eo) } - val hover = presentationCompiler - .hover(pcParams) - .get() + val hover = presentationCompiler.hover(pcParams).get() val obtained: String = - renderAsString(code, hover.asScala.map(_.toLsp), includeRange) + renderAsString( + code, + hover.asScala.map(_.toLsp()), + includeRange, + ) assertNoDiff( obtained, 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] = { 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) } }