From 19d673bcf66b51e12b7b2ffb63b025f72c202294 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 3 Jul 2024 16:07:10 +0200 Subject: [PATCH 1/2] Repl - method signatures in autocomplete (#19917) Closes #17367 This PR brings method signatures to repl completions, as in Scala 2 repl (but with syntax highlighting). After typing `List(1).max`: ![image](https://github.com/scala/scala3/assets/24961583/0846e7b1-ff44-4e95-aaff-398069821b2f) [Cherry-picked b0ba045090b9abbd742859b62b37b43d765c00a6][modified] --- .../src/dotty/tools/repl/ReplDriver.scala | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 36468a72e9cb..27ceb207e1bc 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -31,6 +31,7 @@ import dotty.tools.dotc.util.{SourceFile, SourcePosition} import dotty.tools.dotc.{CompilationUnit, Driver} import dotty.tools.dotc.config.CompilerCommand import dotty.tools.io.* +import dotty.tools.repl.Rendering.showUser import dotty.tools.runner.ScalaClassLoader.* import org.jline.reader.* @@ -148,11 +149,36 @@ class ReplDriver(settings: Array[String], /** Blockingly read a line, getting back a parse result */ def readLine()(using state: State): ParseResult = { - val completer: Completer = { (_, line, candidates) => + given Context = state.context + val completer: Completer = { (lineReader, line, candidates) => + def makeCandidate(label: String) = { + new Candidate( + /* value = */ label, + /* displ = */ stripBackTicks(label), // displayed value + /* group = */ null, // can be used to group completions together + /* descr = */ null, // TODO use for documentation? + /* suffix = */ null, + /* key = */ null, + /* complete = */ false // if true adds space when completing + ) + } val comps = completions(line.cursor, line.line, state) - candidates.addAll(comps.asJava) + candidates.addAll(comps.map(_.label).distinct.map(makeCandidate).asJava) + val lineWord = line.word() + comps.filter(c => c.label == lineWord && c.symbols.nonEmpty) match + case Nil => + case exachMatches => + val terminal = lineReader.nn.getTerminal + lineReader.callWidget(LineReader.CLEAR) + terminal.writer.println() + exachMatches.foreach: exact => + exact.symbols.foreach: sym => + terminal.writer.println(SyntaxHighlighting.highlight(sym.showUser)) + lineReader.callWidget(LineReader.REDRAW_LINE) + lineReader.callWidget(LineReader.REDISPLAY) + terminal.flush() } - given Context = state.context + try { val line = terminal.readLine(completer) ParseResult(line) @@ -229,23 +255,10 @@ class ReplDriver(settings: Array[String], label /** Extract possible completions at the index of `cursor` in `expr` */ - protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] = - def makeCandidate(label: String) = { - - new Candidate( - /* value = */ label, - /* displ = */ stripBackTicks(label), // displayed value - /* group = */ null, // can be used to group completions together - /* descr = */ null, // TODO use for documentation? - /* suffix = */ null, - /* key = */ null, - /* complete = */ false // if true adds space when completing - ) - } - + protected final def completions(cursor: Int, expr: String, state0: State): List[Completion] = if expr.startsWith(":") then ParseResult.commands.collect { - case command if command._1.startsWith(expr) => makeCandidate(command._1) + case command if command._1.startsWith(expr) => Completion(command._1, "", List()) } else given state: State = newRun(state0) @@ -258,8 +271,7 @@ class ReplDriver(settings: Array[String], unit.tpdTree = tpdTree given Context = state.context.fresh.setCompilationUnit(unit) val srcPos = SourcePosition(file, Span(cursor)) - val completions = try Completion.completions(srcPos)._2 catch case NonFatal(_) => Nil - completions.map(_.label).distinct.map(makeCandidate) + try Completion.completions(srcPos)._2 catch case NonFatal(_) => Nil } .getOrElse(Nil) end completions From 99609776eca4c65a65a01f1f65bfca8e6ac07625 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 4 Jul 2024 00:33:03 +0200 Subject: [PATCH 2/2] Bring back old completions as a deprecated method in the LTS --- .../src/dotty/tools/repl/ReplDriver.scala | 21 ++++++++++++++++--- .../dotty/tools/repl/TabcompleteTests.scala | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 27ceb207e1bc..0b96543ed2e5 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -162,7 +162,7 @@ class ReplDriver(settings: Array[String], /* complete = */ false // if true adds space when completing ) } - val comps = completions(line.cursor, line.line, state) + val comps = completionsWithSignatures(line.cursor, line.line, state) candidates.addAll(comps.map(_.label).distinct.map(makeCandidate).asJava) val lineWord = line.word() comps.filter(c => c.label == lineWord && c.symbols.nonEmpty) match @@ -254,8 +254,23 @@ class ReplDriver(settings: Array[String], else label + @deprecated("Use completionsWithSignatures instead", "3.3.4") + protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] = + completionsWithSignatures(cursor, expr, state0).map: c => + new Candidate( + /* value = */ c.label, + /* displ = */ stripBackTicks(c.label), // displayed value + /* group = */ null, // can be used to group completions together + /* descr = */ null, // TODO use for documentation? + /* suffix = */ null, + /* key = */ null, + /* complete = */ false // if true adds space when completing + ) + end completions + + /** Extract possible completions at the index of `cursor` in `expr` */ - protected final def completions(cursor: Int, expr: String, state0: State): List[Completion] = + protected final def completionsWithSignatures(cursor: Int, expr: String, state0: State): List[Completion] = if expr.startsWith(":") then ParseResult.commands.collect { case command if command._1.startsWith(expr) => Completion(command._1, "", List()) @@ -274,7 +289,7 @@ class ReplDriver(settings: Array[String], try Completion.completions(srcPos)._2 catch case NonFatal(_) => Nil } .getOrElse(Nil) - end completions + end completionsWithSignatures protected def interpret(res: ParseResult, quiet: Boolean = false)(using state: State): State = { res match { diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 0bce525e1469..d27dfb27b7b7 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -10,7 +10,7 @@ class TabcompleteTests extends ReplTest { /** Returns the `(, )`*/ private def tabComplete(src: String)(implicit state: State): List[String] = - completions(src.length, src, state).map(_.value).sorted + completionsWithSignatures(src.length, src, state).map(_.label).sorted.distinct @Test def tabCompleteList = initially { val comp = tabComplete("List.r")