Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(repl): autocomplete repl commands #14627

Merged
merged 3 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/repl/JLineTerminal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 :<partial-word> 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)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/repl/ParseResult.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
34 changes: 20 additions & 14 deletions compiler/src/dotty/tools/repl/ReplDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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("<completions>", 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("<completions>", 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 {
Expand Down
24 changes: 24 additions & 0 deletions compiler/test/dotty/tools/repl/TabcompleteTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
}
}