diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index fa7ca5437cd2..91104acda262 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -31,7 +31,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver { override def sourcesRequired = false private val myInitCtx: Context = { - val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive) + val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive).addMode(Mode.ReadComments) rootCtx.setSetting(rootCtx.settings.YretainTrees, true) val ctx = setup(settings.toArray, rootCtx)._2 ctx.initialize()(ctx) diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index e1f99c25b29a..23b2c3f745d3 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -19,7 +19,7 @@ import scala.io.Codec import dotc._ import ast.{Trees, tpd} import core._, core.Decorators.{sourcePos => _, _} -import Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Trees._, Types._ +import Comments._, Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Trees._, Types._ import classpath.ClassPathEntries import reporting._, reporting.diagnostic.MessageContainer import util._ @@ -340,13 +340,16 @@ class DottyLanguageServer extends LanguageServer implicit val ctx = driver.currentCtx val pos = sourcePosition(driver, uri, params.getPosition) - val tp = Interactive.enclosingType(driver.openedTrees(uri), pos) + val trees = driver.openedTrees(uri) + val tp = Interactive.enclosingType(trees, pos) val tpw = tp.widenTermRefExpr if (tpw == NoType) new Hover else { - val str = tpw.show.toString - new Hover(List(JEither.forLeft(str)).asJava, null) + val symbol = Interactive.enclosingSourceSymbol(trees, pos) + val docComment = ctx.docCtx.flatMap(_.docstring(symbol)) + val markedStrings = docMarkedStrings(docComment, tpw.show.toString) + new Hover(markedStrings.map(JEither.forRight(_)).asJava, null) } } @@ -463,6 +466,19 @@ object DottyLanguageServer { item } + private def docMarkedStrings(comment: Option[Comment], typeInfo: String): List[lsp4j.MarkedString] = { + + val docHover = comment.map { comment => + new lsp4j.MarkedString("scala", comment.raw) + } + + val typeInfoHover = new lsp4j.MarkedString() + typeInfoHover.setValue(typeInfo) + + typeInfoHover :: docHover.toList + } + + /** Create an lsp4j.SymbolInfo from a Symbol and a SourcePosition */ def symbolInfo(sym: Symbol, pos: SourcePosition)(implicit ctx: Context): lsp4j.SymbolInformation = { def symbolKind(sym: Symbol)(implicit ctx: Context): lsp4j.SymbolKind = { diff --git a/language-server/test/dotty/tools/languageserver/HoverTest.scala b/language-server/test/dotty/tools/languageserver/HoverTest.scala index 34dac2ce0b91..e8d8761f0b5a 100644 --- a/language-server/test/dotty/tools/languageserver/HoverTest.scala +++ b/language-server/test/dotty/tools/languageserver/HoverTest.scala @@ -7,53 +7,60 @@ import dotty.tools.languageserver.util.Code._ class HoverTest { @Test def hoverOnWhiteSpace0: Unit = - code"$m1 $m2".withSource.hover(m1 to m2, "") + code"$m1 $m2".withSource.hover(m1 to m2, Nil) + + @Test def hoverOnClassShowsDoc: Unit = { + code"""$m1 /** foo */ ${m2}class Foo $m3 $m4""".withSource + .hover(m1 to m2, Nil) + .hover(m2 to m3, List("Foo", "/** foo */")) + .hover(m3 to m4, Nil) + } @Test def hoverOnClass0: Unit = { code"""$m1 ${m2}class Foo $m3 $m4""".withSource - .hover(m1 to m2, "") - .hover(m2 to m3, "Foo") - .hover(m3 to m4, "") + .hover(m1 to m2, Nil) + .hover(m2 to m3, "Foo" :: Nil) + .hover(m3 to m4, Nil) } @Test def hoverOnClass1: Unit = { code"""$m1 ${m2}class Foo { } $m3 $m4""".withSource - .hover(m1 to m2, "") - .hover(m2 to m3, "Foo") - .hover(m3 to m4, "") + .hover(m1 to m2, Nil) + .hover(m2 to m3, "Foo" :: Nil) + .hover(m3 to m4, Nil) } @Test def hoverOnValDef0: Unit = { code"""class Foo { | ${m1}val x = ${m2}8$m3; ${m4}x$m5 |}""".withSource - .hover(m1 to m2, "Int") - .hover(m2 to m3, "Int(8)") - .hover(m4 to m5, "Int") + .hover(m1 to m2, "Int" :: Nil) + .hover(m2 to m3, "Int(8)" :: Nil) + .hover(m4 to m5, "Int" :: Nil) } @Test def hoverOnValDef1: Unit = { code"""class Foo { | ${m1}final val x = 8$m2; ${m3}x$m4 |}""".withSource - .hover(m1 to m2, "Int(8)") - .hover(m3 to m4, "Int(8)") + .hover(m1 to m2, "Int(8)" :: Nil) + .hover(m3 to m4, "Int(8)" :: Nil) } @Test def hoverOnDefDef0: Unit = { code"""class Foo { | ${m1}def x = ${m2}8$m3; ${m4}x$m5 |}""".withSource - .hover(m1 to m2, "Int") - .hover(m2 to m3, "Int(8)") - .hover(m4 to m5, "Int") + .hover(m1 to m2, "Int" :: Nil) + .hover(m2 to m3, "Int(8)" :: Nil) + .hover(m4 to m5, "Int" :: Nil) } @Test def hoverMissingRef0: Unit = { code"""class Foo { | ${m1}x$m2 |}""".withSource - .hover(m1 to m2, "") + .hover(m1 to m2, "" :: Nil) } @Test def hoverFun0: Unit = { @@ -65,10 +72,10 @@ class HoverTest { | ${m5}y($m6)$m7 |} """.withSource - .hover(m1 to m2, "String(\"abc\")") - .hover(m3 to m4, "String") - .hover(m5 to m6, "(): Int") - .hover(m6 to m7, "Int") + .hover(m1 to m2, "String(\"abc\")" :: Nil) + .hover(m3 to m4, "String" :: Nil) + .hover(m5 to m6, "(): Int" :: Nil) + .hover(m6 to m7, "Int" :: Nil) } } diff --git a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala index 5d79ff733a86..d5d4a40cbfeb 100644 --- a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala +++ b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala @@ -25,11 +25,11 @@ class CodeTester(sources: List[SourceWithPositions], actions: List[Action]) { * Perform a hover over `range`, verifies that result matches `expected`. * * @param range The range over which to hover. - * @param expected The expected result. + * @param expected The expected results. * * @see dotty.tools.languageserver.util.actions.CodeHover */ - def hover(range: CodeRange, expected: String): this.type = + def hover(range: CodeRange, expected: List[String]): this.type = doAction(new CodeHover(range, expected)) /** diff --git a/language-server/test/dotty/tools/languageserver/util/actions/CodeHover.scala b/language-server/test/dotty/tools/languageserver/util/actions/CodeHover.scala index 62b1c8564b0f..d8635588f6af 100644 --- a/language-server/test/dotty/tools/languageserver/util/actions/CodeHover.scala +++ b/language-server/test/dotty/tools/languageserver/util/actions/CodeHover.scala @@ -5,6 +5,8 @@ import dotty.tools.languageserver.util.{CodeRange, PositionContext} import org.junit.Assert.{assertEquals, assertNull, assertTrue} +import scala.collection.JavaConverters._ + /** * An action requesting for the info shown when `range` is hovered. * This action corresponds to the `textDocument/hover` method of the Language Server Protocol. @@ -12,17 +14,18 @@ import org.junit.Assert.{assertEquals, assertNull, assertTrue} * @param range The range of positions that should be hovered. * @param expected The expected result. */ -class CodeHover(override val range: CodeRange, expected: String) extends ActionOnRange { +class CodeHover(override val range: CodeRange, expected: List[String]) extends ActionOnRange { override def onMarker(marker: CodeMarker): Exec[Unit] = { val result = server.hover(marker.toTextDocumentPositionParams).get() assertNull(result.getRange) if (expected.isEmpty) assertNull(result.getContents) else { - assertEquals(1, result.getContents.size) - val content = result.getContents.get(0) - assertTrue(content.isLeft) - assertEquals(expected, content.getLeft) + assertEquals(expected.size, result.getContents.size) + expected.zip(result.getContents.asScala).foreach { case (expected, actual) => + assertTrue(actual.isRight) + assertEquals(expected, actual.getRight.getValue) + } } }