diff --git a/build.sbt b/build.sbt index 958d11030..5b3785e5b 100644 --- a/build.sbt +++ b/build.sbt @@ -53,6 +53,18 @@ lazy val fansiVersion = Def.setting { else "0.2.7" } +lazy val interfaces = project + .in(file("mdoc-interfaces")) + .settings( + moduleName := "mdoc-interfaces", + autoScalaLibrary := false, + crossVersion := CrossVersion.disabled, + javacOptions in (Compile / doc) ++= List( + "-tag", + "implNote:a:Implementation Note:" + ) + ) + lazy val runtime = project .settings( moduleName := "mdoc-runtime", @@ -61,6 +73,7 @@ lazy val runtime = project "com.lihaoyi" %% "pprint" % pprintVersion.value ) ) + .dependsOn(interfaces) lazy val mdoc = project .settings( @@ -189,6 +202,7 @@ lazy val plugin = project }, publishLocal := publishLocal .dependsOn( + publishLocal in interfaces, publishLocal in runtime, publishLocal in mdoc, publishLocal in js diff --git a/mdoc-interfaces/src/main/scala/mdoc/interfaces/Diagnostic.java b/mdoc-interfaces/src/main/scala/mdoc/interfaces/Diagnostic.java new file mode 100644 index 000000000..46b22eb59 --- /dev/null +++ b/mdoc-interfaces/src/main/scala/mdoc/interfaces/Diagnostic.java @@ -0,0 +1,9 @@ +package mdoc.interfaces; + +public abstract class Diagnostic { + + public abstract RangePosition position(); + public abstract String message(); + public abstract DiagnosticSeverity severity(); + +} \ No newline at end of file diff --git a/mdoc-interfaces/src/main/scala/mdoc/interfaces/DiagnosticSeverity.java b/mdoc-interfaces/src/main/scala/mdoc/interfaces/DiagnosticSeverity.java new file mode 100644 index 000000000..ed6837e7e --- /dev/null +++ b/mdoc-interfaces/src/main/scala/mdoc/interfaces/DiagnosticSeverity.java @@ -0,0 +1,3 @@ +package mdoc.interfaces; + +public enum DiagnosticSeverity { Info, Warning, Error} \ No newline at end of file diff --git a/mdoc-interfaces/src/main/scala/mdoc/interfaces/EvaluatedWorksheet.java b/mdoc-interfaces/src/main/scala/mdoc/interfaces/EvaluatedWorksheet.java new file mode 100644 index 000000000..8bc30b29c --- /dev/null +++ b/mdoc-interfaces/src/main/scala/mdoc/interfaces/EvaluatedWorksheet.java @@ -0,0 +1,10 @@ +package mdoc.interfaces; + +import java.util.List; + +public abstract class EvaluatedWorksheet { + + public abstract List diagnostics(); + public abstract List statements(); + +} diff --git a/mdoc-interfaces/src/main/scala/mdoc/interfaces/EvaluatedWorksheetStatement.java b/mdoc-interfaces/src/main/scala/mdoc/interfaces/EvaluatedWorksheetStatement.java new file mode 100644 index 000000000..54c13a90c --- /dev/null +++ b/mdoc-interfaces/src/main/scala/mdoc/interfaces/EvaluatedWorksheetStatement.java @@ -0,0 +1,9 @@ +package mdoc.interfaces; + +public abstract class EvaluatedWorksheetStatement { + + public abstract RangePosition position(); + public abstract String summary(); + public abstract String details(); + +} diff --git a/mdoc-interfaces/src/main/scala/mdoc/interfaces/Mdoc.java b/mdoc-interfaces/src/main/scala/mdoc/interfaces/Mdoc.java new file mode 100644 index 000000000..ccc2c9024 --- /dev/null +++ b/mdoc-interfaces/src/main/scala/mdoc/interfaces/Mdoc.java @@ -0,0 +1,18 @@ +package mdoc.interfaces; + +import java.util.List; +import java.nio.file.Path; +import java.io.PrintStream; + +public abstract class Mdoc { + + public abstract EvaluatedWorksheet evaluateWorksheet(String filename, String text); + public abstract Mdoc withClasspath(List classpath); + public abstract Mdoc withScalacOptions(List options); + public abstract Mdoc withSettings(List settings); + public abstract Mdoc withConsoleReporter(PrintStream out); + public abstract Mdoc withScreenHeight(int screenHeight); + public abstract Mdoc withScreenWidth(int screenWidth); + public abstract void shutdown(); + +} diff --git a/mdoc-interfaces/src/main/scala/mdoc/interfaces/RangePosition.java b/mdoc-interfaces/src/main/scala/mdoc/interfaces/RangePosition.java new file mode 100644 index 000000000..95e77dbdd --- /dev/null +++ b/mdoc-interfaces/src/main/scala/mdoc/interfaces/RangePosition.java @@ -0,0 +1,8 @@ +package mdoc.interfaces; + +public abstract class RangePosition { + public abstract int startLine(); + public abstract int startColumn(); + public abstract int endLine(); + public abstract int endColumn(); +} diff --git a/mdoc/src/main/resources/META-INF/services/mdoc.interfaces.Mdoc b/mdoc/src/main/resources/META-INF/services/mdoc.interfaces.Mdoc new file mode 100644 index 000000000..3e96fab23 --- /dev/null +++ b/mdoc/src/main/resources/META-INF/services/mdoc.interfaces.Mdoc @@ -0,0 +1 @@ +mdoc.internal.worksheets.Mdoc diff --git a/mdoc/src/main/scala-2.11/mdoc/internal/worksheets/Compat.scala b/mdoc/src/main/scala-2.11/mdoc/internal/worksheets/Compat.scala new file mode 100644 index 000000000..6b50bf50f --- /dev/null +++ b/mdoc/src/main/scala-2.11/mdoc/internal/worksheets/Compat.scala @@ -0,0 +1,10 @@ +package mdoc.internal.worksheets + +import scala.tools.nsc.Global + +object Compat { + def usedDummy() = () // Only here to avoid "unused import" warning. + implicit class XtensionCompiler(compiler: Global) { + def close(): Unit = () // do nothing, not available + } +} diff --git a/mdoc/src/main/scala-2.12/mdoc/internal/worksheets/Compat.scala b/mdoc/src/main/scala-2.12/mdoc/internal/worksheets/Compat.scala new file mode 100644 index 000000000..6ffca54e2 --- /dev/null +++ b/mdoc/src/main/scala-2.12/mdoc/internal/worksheets/Compat.scala @@ -0,0 +1,5 @@ +package mdoc.internal.worksheets + +object Compat { + def usedDummy() = () // Only here to avoid "unused import" warning. +} diff --git a/mdoc/src/main/scala-2.13/mdoc/internal/worksheets/Compat.scala b/mdoc/src/main/scala-2.13/mdoc/internal/worksheets/Compat.scala new file mode 100644 index 000000000..6ffca54e2 --- /dev/null +++ b/mdoc/src/main/scala-2.13/mdoc/internal/worksheets/Compat.scala @@ -0,0 +1,5 @@ +package mdoc.internal.worksheets + +object Compat { + def usedDummy() = () // Only here to avoid "unused import" warning. +} diff --git a/mdoc/src/main/scala/mdoc/MainSettings.scala b/mdoc/src/main/scala/mdoc/MainSettings.scala index 2088e3e9e..53ba30f43 100644 --- a/mdoc/src/main/scala/mdoc/MainSettings.scala +++ b/mdoc/src/main/scala/mdoc/MainSettings.scala @@ -92,6 +92,12 @@ final class MainSettings private ( def withVariablePrinter(variablePrinter: Variable => String): MainSettings = { copy(settings.copy(variablePrinter = variablePrinter)) } + def withScreenWidth(screenWidth: Int): MainSettings = { + copy(settings.copy(screenWidth = screenWidth)) + } + def withScreenHeight(screenHeight: Int): MainSettings = { + copy(settings.copy(screenHeight = screenHeight)) + } private[this] implicit def cwd: AbsolutePath = settings.cwd private[this] def copy( diff --git a/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala b/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala index e76aa9c00..8a9a3a496 100644 --- a/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala +++ b/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala @@ -130,6 +130,12 @@ case class Settings( @Description("The input stream to listen for enter key during file watching.") inputStream: InputStream = System.in, @Hidden() + @Description("The width of the screen, used to line wrap pretty-printed objects.") + screenWidth: Int = 120, + @Hidden() + @Description("The height of the screen, used to truncate large pretty-printed objects.") + screenHeight: Int = 50, + @Hidden() @Description("The generator for header IDs, defaults to GitHub ID generator") headerIdGenerator: String => String = GitHubIdGenerator, @Hidden() diff --git a/mdoc/src/main/scala/mdoc/internal/io/Diagnostic.scala b/mdoc/src/main/scala/mdoc/internal/io/Diagnostic.scala new file mode 100644 index 000000000..22933bd44 --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/io/Diagnostic.scala @@ -0,0 +1,10 @@ +package mdoc.internal.io + +import mdoc.interfaces.DiagnosticSeverity +import mdoc.interfaces.RangePosition + +case class Diagnostic( + val position: RangePosition, + val message: String, + val severity: DiagnosticSeverity +) extends mdoc.interfaces.Diagnostic diff --git a/mdoc/src/main/scala/mdoc/internal/io/StoreReporter.scala b/mdoc/src/main/scala/mdoc/internal/io/StoreReporter.scala new file mode 100644 index 000000000..2a0862132 --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/io/StoreReporter.scala @@ -0,0 +1,49 @@ +package mdoc.internal.io + +import scala.meta._ +import scala.collection.mutable +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import mdoc.interfaces.DiagnosticSeverity +import mdoc.document.RangePosition +import mdoc.internal.pos.PositionSyntax._ + +class StoreReporter() extends ConsoleReporter(System.out) { + val diagnostics: mutable.LinkedHashSet[Diagnostic] = + mutable.LinkedHashSet.empty[Diagnostic] + + override def reset(): Unit = diagnostics.clear() + + override def warningCount: Int = + diagnostics.count(_.severity == DiagnosticSeverity.Warning) + override def errorCount: Int = + diagnostics.count(_.severity == DiagnosticSeverity.Error) + override def hasErrors: Boolean = errorCount > 0 + override def hasWarnings: Boolean = warningCount > 0 + + override def warning(pos: Position, msg: String): Unit = { + diagnostics += new Diagnostic( + pos.toMdoc, + msg, + DiagnosticSeverity.Warning + ) + super.warning(pos, msg) + } + override def error(pos: Position, throwable: Throwable): Unit = { + val out = new ByteArrayOutputStream() + throwable.printStackTrace(new PrintStream(out)) + diagnostics += new Diagnostic( + pos.toMdoc, + out.toString(), + DiagnosticSeverity.Error + ) + } + override def error(pos: Position, msg: String): Unit = { + diagnostics += new Diagnostic( + pos.toMdoc, + msg, + DiagnosticSeverity.Error + ) + super.error(pos, msg) + } +} diff --git a/mdoc/src/main/scala/mdoc/internal/pos/PositionSyntax.scala b/mdoc/src/main/scala/mdoc/internal/pos/PositionSyntax.scala index 74cc595ad..6feb335ab 100644 --- a/mdoc/src/main/scala/mdoc/internal/pos/PositionSyntax.scala +++ b/mdoc/src/main/scala/mdoc/internal/pos/PositionSyntax.scala @@ -87,6 +87,13 @@ object PositionSyntax { case _ => pos } + def toMdoc: RangePosition = + new RangePosition( + pos.startLine, + pos.startColumn, + pos.endLine, + pos.endColumn + ) def contains(offset: Int): Boolean = { if (pos.start == pos.end) pos.end == offset else { diff --git a/mdoc/src/main/scala/mdoc/internal/worksheets/EvaluatedWorksheet.scala b/mdoc/src/main/scala/mdoc/internal/worksheets/EvaluatedWorksheet.scala new file mode 100644 index 000000000..9e50507af --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/worksheets/EvaluatedWorksheet.scala @@ -0,0 +1,9 @@ +package mdoc.internal.worksheets + +import java.{util => ju} +import mdoc.{interfaces => i} + +case class EvaluatedWorksheet( + val diagnostics: ju.List[i.Diagnostic], + val statements: ju.List[i.EvaluatedWorksheetStatement] +) extends mdoc.interfaces.EvaluatedWorksheet diff --git a/mdoc/src/main/scala/mdoc/internal/worksheets/EvaluatedWorksheetStatement.scala b/mdoc/src/main/scala/mdoc/internal/worksheets/EvaluatedWorksheetStatement.scala new file mode 100644 index 000000000..5456f52a3 --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/worksheets/EvaluatedWorksheetStatement.scala @@ -0,0 +1,8 @@ +package mdoc.internal.worksheets +import mdoc.interfaces.RangePosition + +case class EvaluatedWorksheetStatement( + val position: RangePosition, + val summary: String, + val details: String +) extends mdoc.interfaces.EvaluatedWorksheetStatement diff --git a/mdoc/src/main/scala/mdoc/internal/worksheets/Mdoc.scala b/mdoc/src/main/scala/mdoc/internal/worksheets/Mdoc.scala new file mode 100644 index 000000000..f45b24cf3 --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/worksheets/Mdoc.scala @@ -0,0 +1,63 @@ +package mdoc.internal.worksheets + +import java.{util => ju} +import java.io.File +import java.io.PrintStream +import java.nio.file.Path +import scala.collection.JavaConverters._ +import mdoc.{interfaces => i} +import mdoc.internal.cli.Context +import mdoc.internal.cli.Settings +import scala.meta.internal.io.PathIO +import mdoc.internal.io.ConsoleReporter +import mdoc.internal.markdown.MarkdownCompiler +import scala.meta.inputs.Input +import mdoc.internal.worksheets.Compat._ +import mdoc.MainSettings + +class Mdoc(settings: MainSettings) extends i.Mdoc { + + private var myContext: Context = null + + def this() = this(MainSettings()) + + def withClasspath(classpath: ju.List[Path]): i.Mdoc = + new Mdoc(this.settings.withClasspath(classpath.iterator().asScala.mkString(File.pathSeparator))) + def withScalacOptions(options: ju.List[String]): i.Mdoc = + new Mdoc(this.settings.withScalacOptions(options.iterator().asScala.mkString(" "))) + def withSettings(settings: ju.List[String]): i.Mdoc = + new Mdoc(this.settings.withArgs(settings.iterator().asScala.toList)) + def withConsoleReporter(out: PrintStream): i.Mdoc = + new Mdoc(this.settings.withReporter(new ConsoleReporter(out))) + def withScreenWidth(screenWidth: Int): i.Mdoc = + new Mdoc(this.settings.withScreenWidth(screenWidth)) + def withScreenHeight(screenHeight: Int): i.Mdoc = + new Mdoc(this.settings.withScreenHeight(screenHeight)) + + def shutdown(): Unit = { + if (myContext != null) { + myContext.compiler.global.close() + usedDummy() + } + } + + def evaluateWorksheet(filename: String, text: String): EvaluatedWorksheet = + new WorksheetProvider(settings.settings).evaluateWorksheet( + Input.VirtualFile(filename, text), + context + ) + + private def context(): Context = { + if (myContext == null) { + myContext = Context( + settings.settings, + settings.reporter, + MarkdownCompiler.fromClasspath( + settings.settings.classpath, + settings.settings.scalacOptions + ) + ) + } + myContext + } +} diff --git a/mdoc/src/main/scala/mdoc/internal/worksheets/WorksheetProvider.scala b/mdoc/src/main/scala/mdoc/internal/worksheets/WorksheetProvider.scala new file mode 100644 index 000000000..ddea2443c --- /dev/null +++ b/mdoc/src/main/scala/mdoc/internal/worksheets/WorksheetProvider.scala @@ -0,0 +1,152 @@ +package mdoc.internal.worksheets + +import scala.meta._ +import mdoc.internal.cli.Context +import scala.collection.JavaConverters._ +import mdoc.internal.markdown.SectionInput +import scala.meta.parsers.Parsed.Success +import mdoc.internal.markdown.Modifier +import mdoc.internal.markdown.Instrumenter +import mdoc.internal.markdown.MarkdownCompiler +import mdoc.document.Statement +import mdoc.document.RangePosition +import mdoc.internal.cli.Settings +import pprint.TPrintColors +import pprint.PPrinter.BlackWhite +import mdoc.internal.io.StoreReporter +import mdoc.interfaces.Diagnostic +import mdoc.{interfaces => i} +import mdoc.internal.markdown.MdocDialect + +class WorksheetProvider(settings: Settings) { + + private val reporter = new StoreReporter() + + private val commentHeader = " // " + // The smallest column width that worksheet values will use for rendering + // worksheet decorations. + private val minimumMargin = 20 + + def evaluateWorksheet( + input: Input.VirtualFile, + ctx: Context + ): EvaluatedWorksheet = { + val source = MdocDialect.scala(input).parse[Source].getOrElse(Source(Nil)) + val sectionInput = SectionInput( + input, + source, + Modifier.Default() + ) + val sectionInputs = List(sectionInput) + val instrumented = Instrumenter.instrument(sectionInputs) + val rendered = MarkdownCompiler.buildDocument( + ctx.compiler, + reporter, + sectionInputs, + instrumented, + input.path + ) + + val decorations = for { + section <- rendered.sections.iterator + statement <- section.section.statements + } yield renderDecoration(statement) + + EvaluatedWorksheet( + reporter.diagnostics.map(d => d: i.Diagnostic).toSeq.asJava, + decorations.toIterator + .filterNot(_.summary == commentHeader) + .map(d => d: i.EvaluatedWorksheetStatement) + .toList + .asJava + ) + } + + private def renderDecoration(statement: Statement): EvaluatedWorksheetStatement = { + val pos = statement.position + val range = new RangePosition( + pos.startLine, + pos.startColumn, + pos.endLine, + pos.endColumn + ) + val margin = math.max( + minimumMargin, + settings.screenWidth - statement.position.endColumn + ) + val isEmptyValue = isUnitType(statement) || statement.binders.isEmpty + val contentText = renderContentText(statement, margin, isEmptyValue) + val hoverMessage = renderHoverMessage(statement, margin, isEmptyValue) + EvaluatedWorksheetStatement(range, contentText, hoverMessage) + } + + private def renderHoverMessage( + statement: Statement, + margin: Int, + isEmptyValue: Boolean + ): String = { + val out = new StringBuilder() + if (!isEmptyValue) { + statement.binders.iterator.foreach { binder => + out + .append("\n") + .append(binder.name) + .append(": ") + .append(binder.tpe.render(TPrintColors.BlackWhite)) + .append(" = ") + BlackWhite + .tokenize(binder.value, width = settings.screenWidth, height = settings.screenHeight) + .foreach(text => out.appendAll(text.getChars)) + } + } + statement.out.linesIterator.foreach { line => + out.append("\n// ").append(line) + } + out.toString() + } + + private def renderContentText( + statement: Statement, + margin: Int, + isEmptyValue: Boolean + ): String = { + val out = new StringBuilder() + out.append(commentHeader) + if (isEmptyValue) { + if (!statement.out.isEmpty()) { + out.append(statement.out.linesIterator.next()) + } + } else { + val isSingle = statement.binders.lengthCompare(1) == 0 + statement.binders.iterator.zipWithIndex.foreach { + case (binder, i) => + if (!isSingle) { + out + .append(if (i == 0) "" else ", ") + .append(binder.name) + .append("=") + } + val truncatedLine = BlackWhite + .tokenize(binder.value, width = margin, height = settings.screenHeight) + .map(_.getChars) + .filterNot(_.iterator.forall(_.isWhitespace)) + .flatMap(_.iterator) + .filter { + case '\n' => false + case _ => true + } + .take(margin) + out.appendAll(truncatedLine) + } + } + out.toString() + } + + private def isUnitType(statement: Statement): Boolean = { + statement.binders match { + case head :: Nil => () == head.value + case _ => false + } + + } +} diff --git a/runtime/src/main/scala/mdoc/document/RangePosition.scala b/runtime/src/main/scala/mdoc/document/RangePosition.scala index 80c5424e4..14f69398d 100644 --- a/runtime/src/main/scala/mdoc/document/RangePosition.scala +++ b/runtime/src/main/scala/mdoc/document/RangePosition.scala @@ -9,7 +9,7 @@ final class RangePosition( val startColumn: Int, val endLine: Int, val endColumn: Int -) { +) extends mdoc.interfaces.RangePosition { def add(other: RangePosition): RangePosition = new RangePosition( other.startLine + startLine, diff --git a/tests/unit/src/test/scala/tests/worksheets/WorksheetSuite.scala b/tests/unit/src/test/scala/tests/worksheets/WorksheetSuite.scala new file mode 100644 index 000000000..f55eef66f --- /dev/null +++ b/tests/unit/src/test/scala/tests/worksheets/WorksheetSuite.scala @@ -0,0 +1,245 @@ +package tests.worksheets + +import java.lang.StringBuilder +import org.scalatest.FunSuite +import org.scalatest.BeforeAndAfterAll +import mdoc.interfaces.Mdoc +import scala.meta.testkit.DiffAssertions +import scala.collection.JavaConverters._ +import scala.meta.inputs.Input +import scala.meta.inputs.Position +import mdoc.document.RangePosition +import mdoc.internal.pos.PositionSyntax._ +import mdoc.interfaces.DiagnosticSeverity +import java.{util => ju} +import mdoc.PostModifier + +class WorksheetSuite extends FunSuite with BeforeAndAfterAll with DiffAssertions { + var mdoc = ju.ServiceLoader + .load(classOf[Mdoc], this.getClass().getClassLoader()) + .iterator() + .next() + .withScreenWidth(30) + .withScreenHeight(5) + override def afterAll(): Unit = { + mdoc.shutdown() + } + + def checkDiagnostics( + name: String, + original: String, + expected: String + ): Unit = { + test(name) { + val filename = name + ".scala" + val worksheet = mdoc.evaluateWorksheet(filename, original) + assert(worksheet.statements.isEmpty, worksheet.statements) + val input = Input.VirtualFile(name, original) + val out = new StringBuilder() + var i = 0 + val diagnostics = + worksheet.diagnostics.asScala.filter(_.severity() == DiagnosticSeverity.Error) + diagnostics.foreach { diag => + val p = Position.Range( + input, + diag.position().startLine(), + diag.position().startColumn(), + diag.position().endLine(), + diag.position().endColumn() + ) + val message = + p.formatMessage(diag.severity().toString().toLowerCase(), diag.message()) + out.append(message).append("\n") + } + val obtained = out.toString() + assertNoDiffOrPrintExpected(obtained, expected) + } + } + + def checkDecorations( + name: String, + original: String, + expected: String + ): Unit = { + test(name) { + val filename = name + ".scala" + val worksheet = mdoc.evaluateWorksheet(filename, original) + val statements = worksheet.statements().asScala.sortBy(_.position().startLine()) + val input = Input.VirtualFile(name, original) + val out = new StringBuilder() + var i = 0 + val hasErrors = + worksheet.diagnostics().asScala.exists(_.severity() == DiagnosticSeverity.Error) + require(!hasErrors, worksheet.diagnostics()) + statements.foreach { stat => + val p = Position.Range( + input, + stat.position().startLine(), + stat.position().startColumn(), + stat.position().endLine(), + stat.position().endColumn() + ) + out + .append(original, i, p.start) + .append("<") + .append(p.text) + .append(">") + .append(stat.summary()) + .append("\n") + .append(stat.details()) + i = p.end + } + val obtained = out.toString() + assertNoDiffOrPrintExpected(obtained, expected) + } + } + + checkDecorations( + "basic", + """ + |val x = 1.to(4).toVector + |""".stripMargin, + """| + | // Vector(1, 2, 3, 4) + | + |x: Vector[Int] = Vector(1, 2, 3, 4) + |""".stripMargin + ) + + checkDecorations( + "binders", + """ + |val List(x, y) = List(1, 2) + |""".stripMargin, + """| + | // x=1, y=2 + | + |x: Int = 1 + |y: Int = 2 + |""".stripMargin + ) + + checkDecorations( + "stream", + """ + |Stream.from(10) + |""".stripMargin, + """| + | // Stream(10,11,12,... + | + |res0: Stream[Int] = Stream( + | 10, + | 11, + | 12, + |... + |""".stripMargin + ) + + checkDecorations( + "stdout", + """ + |println(1.to(3).mkString(";\n")) + |""".stripMargin, + """| + | // 1; + | + |// 1; + |// 2; + |// 3 + |""".stripMargin + ) + + checkDecorations( + "stdout+value", + """ + |val x = { + | println("hello") + | 42 + |} + |""".stripMargin, + """| + | // 42 + | + |x: Int = 42 + |// hello + |""".stripMargin + ) + + checkDecorations( + "multi-statements", + """ + |val n = 10 + |println(n) + |val m = n * 10 + |""".stripMargin, + """| + | // 10 + | + |n: Int = 10 + | // 10 + | + |// 10 + | // 100 + | + |m: Int = 100 + |""".stripMargin + ) + + checkDecorations( + "imports", + """import scala.concurrent.Future + |val n = Future.successful(10) + |""".stripMargin, + """|import scala.concurrent.Future + | // Future(Success(10)) + | + |n: Future[Int] = Future(Success(10)) + |""".stripMargin + ) + + checkDecorations( + "definition", + """case class User(name: String) + |val n = User("Susan") + |""".stripMargin, + """|case class User(name: String) + | // User("Susan") + | + |n: User = User("Susan") + |""".stripMargin + ) + + checkDiagnostics( + "type-error", + """ + |val filename: Int = "not found" + |""".stripMargin, + """|type-error:2:21: error: type mismatch; + | found : String("not found") + | required: Int + |val filename: Int = "not found" + | ^^^^^^^^^^^ + |""".stripMargin + ) + + checkDiagnostics( + "crash", + """ + |def crash(msg: String) = throw new RuntimeException(msg) + |val filename = "boom" + |crash(filename) + |""".stripMargin, + """|crash:4:1: error: java.lang.RuntimeException: boom + | at repl.Session$App.crash(crash.scala:8) + | at repl.Session$App.(crash.scala:14) + | at repl.Session$.app(crash.scala:3) + | + |crash(filename) + |^^^^^^^^^^^^^^^ + |""".stripMargin + ) + +}