Skip to content

Commit f7ede22

Browse files
authored
Merge pull request #14627 from ckipp01/commands
feat(repl): autocomplete repl commands
2 parents 99c2b96 + 3323d5d commit f7ede22

File tree

4 files changed

+53
-15
lines changed

4 files changed

+53
-15
lines changed

compiler/src/dotty/tools/repl/JLineTerminal.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ final class JLineTerminal extends java.io.Closeable {
152152
// using dummy values, resulting parsed input is probably unused
153153
defaultParsedLine
154154

155+
// In the situation where we have a partial command that we want to
156+
// complete we need to ensure that the :<partial-word> isn't split into
157+
// 2 tokens, but rather the entire thing is treated as the "word", in
158+
// order to insure the : is replaced in the completion.
159+
case ParseContext.COMPLETE if
160+
ParseResult.commands.exists(command => command._1.startsWith(input)) =>
161+
parsedLine(input, cursor)
162+
155163
case ParseContext.COMPLETE =>
156164
// Parse to find completions (typically after a Tab).
157165
def isCompletable(token: Token) = isIdentifier(token) || isKeyword(token)

compiler/src/dotty/tools/repl/ParseResult.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ object ParseResult {
127127
stats
128128
}
129129

130-
private val commands: List[(String, String => ParseResult)] = List(
130+
private[repl] val commands: List[(String, String => ParseResult)] = List(
131131
Quit.command -> (_ => Quit),
132132
Quit.alias -> (_ => Quit),
133133
Help.command -> (_ => Help),

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ class ReplDriver(settings: Array[String],
205205
label
206206

207207
/** Extract possible completions at the index of `cursor` in `expr` */
208-
protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] = {
208+
protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] =
209209
def makeCandidate(label: String) = {
210210

211211
new Candidate(
@@ -218,20 +218,26 @@ class ReplDriver(settings: Array[String],
218218
/* complete = */ false // if true adds space when completing
219219
)
220220
}
221-
implicit val state = newRun(state0)
222-
compiler
223-
.typeCheck(expr, errorsAllowed = true)
224-
.map { tree =>
225-
val file = SourceFile.virtual("<completions>", expr, maybeIncomplete = true)
226-
val unit = CompilationUnit(file)(using state.context)
227-
unit.tpdTree = tree
228-
given Context = state.context.fresh.setCompilationUnit(unit)
229-
val srcPos = SourcePosition(file, Span(cursor))
230-
val (_, completions) = Completion.completions(srcPos)
231-
completions.map(_.label).distinct.map(makeCandidate)
221+
222+
if expr.startsWith(":") then
223+
ParseResult.commands.collect {
224+
case command if command._1.startsWith(expr) => makeCandidate(command._1)
232225
}
233-
.getOrElse(Nil)
234-
}
226+
else
227+
given state: State = newRun(state0)
228+
compiler
229+
.typeCheck(expr, errorsAllowed = true)
230+
.map { tree =>
231+
val file = SourceFile.virtual("<completions>", expr, maybeIncomplete = true)
232+
val unit = CompilationUnit(file)(using state.context)
233+
unit.tpdTree = tree
234+
given Context = state.context.fresh.setCompilationUnit(unit)
235+
val srcPos = SourcePosition(file, Span(cursor))
236+
val (_, completions) = Completion.completions(srcPos)
237+
completions.map(_.label).distinct.map(makeCandidate)
238+
}
239+
.getOrElse(Nil)
240+
end completions
235241

236242
private def interpret(res: ParseResult)(implicit state: State): State = {
237243
res match {

compiler/test/dotty/tools/repl/TabcompleteTests.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,28 @@ class TabcompleteTests extends ReplTest {
195195
|Foo.`bac"""stripMargin))
196196
}
197197

198+
@Test def commands = initially {
199+
assertEquals(
200+
List(
201+
":doc",
202+
":exit",
203+
":help",
204+
":imports",
205+
":load",
206+
":quit",
207+
":reset",
208+
":settings",
209+
":type"
210+
),
211+
tabComplete(":")
212+
)
213+
}
214+
215+
@Test def commandPreface = initially {
216+
// This looks odd, but if we return :doc here it will result in ::doc in the REPL
217+
assertEquals(
218+
List(":doc"),
219+
tabComplete(":d")
220+
)
221+
}
198222
}

0 commit comments

Comments
 (0)