diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index 69a9b1119c39..9da12ae955d1 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -152,6 +152,14 @@ final class JLineTerminal extends java.io.Closeable { // using dummy values, resulting parsed input is probably unused defaultParsedLine + // In the situation where we have a partial command that we want to + // complete we need to ensure that the : isn't split into + // 2 tokens, but rather the entire thing is treated as the "word", in + // order to insure the : is replaced in the completion. + case ParseContext.COMPLETE if + ParseResult.commands.exists(command => command._1.startsWith(input)) => + parsedLine(input, cursor) + case ParseContext.COMPLETE => // Parse to find completions (typically after a Tab). def isCompletable(token: Token) = isIdentifier(token) || isKeyword(token) diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 5d414ecf9b34..effd7740517f 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -127,7 +127,7 @@ object ParseResult { stats } - private val commands: List[(String, String => ParseResult)] = List( + private[repl] val commands: List[(String, String => ParseResult)] = List( Quit.command -> (_ => Quit), Quit.alias -> (_ => Quit), Help.command -> (_ => Help), diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 2ae38e2d0c84..337f87725762 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -205,7 +205,7 @@ 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] = { + protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] = def makeCandidate(label: String) = { new Candidate( @@ -218,20 +218,26 @@ class ReplDriver(settings: Array[String], /* complete = */ false // if true adds space when completing ) } - implicit val state = newRun(state0) - compiler - .typeCheck(expr, errorsAllowed = true) - .map { tree => - val file = SourceFile.virtual("", expr, maybeIncomplete = true) - val unit = CompilationUnit(file)(using state.context) - unit.tpdTree = tree - given Context = state.context.fresh.setCompilationUnit(unit) - val srcPos = SourcePosition(file, Span(cursor)) - val (_, completions) = Completion.completions(srcPos) - completions.map(_.label).distinct.map(makeCandidate) + + if expr.startsWith(":") then + ParseResult.commands.collect { + case command if command._1.startsWith(expr) => makeCandidate(command._1) } - .getOrElse(Nil) - } + else + given state: State = newRun(state0) + compiler + .typeCheck(expr, errorsAllowed = true) + .map { tree => + val file = SourceFile.virtual("", expr, maybeIncomplete = true) + val unit = CompilationUnit(file)(using state.context) + unit.tpdTree = tree + given Context = state.context.fresh.setCompilationUnit(unit) + val srcPos = SourcePosition(file, Span(cursor)) + val (_, completions) = Completion.completions(srcPos) + completions.map(_.label).distinct.map(makeCandidate) + } + .getOrElse(Nil) + end completions private def interpret(res: ParseResult)(implicit state: State): State = { res match { diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 61257f99a9c3..555137226bcb 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -191,4 +191,28 @@ class TabcompleteTests extends ReplTest { |Foo.`bac"""stripMargin)) } + @Test def commands = initially { + assertEquals( + List( + ":doc", + ":exit", + ":help", + ":imports", + ":load", + ":quit", + ":reset", + ":settings", + ":type" + ), + tabComplete(":") + ) + } + + @Test def commandPreface = initially { + // This looks odd, but if we return :doc here it will result in ::doc in the REPL + assertEquals( + List(":doc"), + tabComplete(":d") + ) + } }