Skip to content

Commit b6cf280

Browse files
committed
feat(repl): method signatures in autocomplete
1 parent 46b7bf5 commit b6cf280

File tree

2 files changed

+52
-35
lines changed

2 files changed

+52
-35
lines changed

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

+49-34
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import dotty.tools.dotc.util.{SourceFile, SourcePosition}
3131
import dotty.tools.dotc.{CompilationUnit, Driver}
3232
import dotty.tools.dotc.config.CompilerCommand
3333
import dotty.tools.io.*
34+
import dotty.tools.repl.Rendering.showUser
3435
import dotty.tools.runner.ScalaClassLoader.*
3536
import org.jline.reader.*
3637

@@ -149,11 +150,38 @@ class ReplDriver(settings: Array[String],
149150

150151
/** Blockingly read a line, getting back a parse result */
151152
def readLine()(using state: State): ParseResult = {
152-
val completer: Completer = { (_, line, candidates) =>
153-
val comps = completions(line.cursor, line.line, state)
154-
candidates.addAll(comps.asJava)
155-
}
156153
given Context = state.context
154+
val completer: Completer = { (lineReader, line, candidates) =>
155+
def makeCandidate(label: String) = {
156+
new Candidate(
157+
/* value = */ label,
158+
/* displ = */ stripBackTicks(label), // displayed value
159+
/* group = */ null, // can be used to group completions together
160+
/* descr = */ null, // TODO use for documentation?
161+
/* suffix = */ null,
162+
/* key = */ null,
163+
/* complete = */ false // if true adds space when completing
164+
)
165+
}
166+
completions(line.cursor, line.line, state) match
167+
case Left(cmds) => candidates.addAll(cmds.map(makeCandidate).asJava)
168+
case Right(comps) =>
169+
val lineWord = line.word()
170+
candidates.addAll(comps.map(c => makeCandidate(c.label)).asJava)
171+
comps.filter(_.label == lineWord) match
172+
case Nil =>
173+
case exachMatches =>
174+
val terminal = lineReader.getTerminal
175+
lineReader.callWidget(LineReader.CLEAR)
176+
terminal.writer.println()
177+
exachMatches.foreach: exact =>
178+
exact.symbols.foreach: sym =>
179+
terminal.writer.println(SyntaxHighlighting.highlight(sym.showUser))
180+
lineReader.callWidget(LineReader.REDRAW_LINE)
181+
lineReader.callWidget(LineReader.REDISPLAY)
182+
terminal.flush()
183+
}
184+
157185
try {
158186
val line = terminal.readLine(completer)
159187
ParseResult(line)
@@ -230,39 +258,26 @@ class ReplDriver(settings: Array[String],
230258
label
231259

232260
/** Extract possible completions at the index of `cursor` in `expr` */
233-
protected final def completions(cursor: Int, expr: String, state0: State): List[Candidate] =
234-
def makeCandidate(label: String) = {
235-
236-
new Candidate(
237-
/* value = */ label,
238-
/* displ = */ stripBackTicks(label), // displayed value
239-
/* group = */ null, // can be used to group completions together
240-
/* descr = */ null, // TODO use for documentation?
241-
/* suffix = */ null,
242-
/* key = */ null,
243-
/* complete = */ false // if true adds space when completing
244-
)
245-
}
246-
261+
protected final def completions(cursor: Int, expr: String, state0: State): Either[List[String], List[Completion]] =
247262
if expr.startsWith(":") then
248-
ParseResult.commands.collect {
249-
case command if command._1.startsWith(expr) => makeCandidate(command._1)
250-
}
263+
Left(ParseResult.commands.collect {
264+
case command if command._1.startsWith(expr) => command._1
265+
})
251266
else
252267
given state: State = newRun(state0)
253-
compiler
254-
.typeCheck(expr, errorsAllowed = true)
255-
.map { (untpdTree, tpdTree) =>
256-
val file = SourceFile.virtual("<completions>", expr, maybeIncomplete = true)
257-
val unit = CompilationUnit(file)(using state.context)
258-
unit.untpdTree = untpdTree
259-
unit.tpdTree = tpdTree
260-
given Context = state.context.fresh.setCompilationUnit(unit)
261-
val srcPos = SourcePosition(file, Span(cursor))
262-
val completions = try Completion.completions(srcPos)._2 catch case NonFatal(_) => Nil
263-
completions.map(_.label).distinct.map(makeCandidate)
264-
}
265-
.getOrElse(Nil)
268+
Right:
269+
compiler
270+
.typeCheck(expr, errorsAllowed = true)
271+
.map { (untpdTree, tpdTree) =>
272+
val file = SourceFile.virtual("<completions>", expr, maybeIncomplete = true)
273+
val unit = CompilationUnit(file)(using state.context)
274+
unit.untpdTree = untpdTree
275+
unit.tpdTree = tpdTree
276+
given Context = state.context.fresh.setCompilationUnit(unit)
277+
val srcPos = SourcePosition(file, Span(cursor))
278+
try Completion.completions(srcPos)._2 catch case NonFatal(_) => Nil
279+
}
280+
.getOrElse(Nil)
266281
end completions
267282

268283
protected def interpret(res: ParseResult, quiet: Boolean = false)(using state: State): State = {

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ extends ReplDriver(options, new PrintStream(out, true, StandardCharsets.UTF_8.na
4242

4343
/** Returns the `(<instance completions>, <companion completions>)`*/
4444
def tabComplete(src: String)(implicit state: State): List[String] =
45-
completions(src.length, src, state).map(_.value).sorted
45+
completions(src.length, src, state) match
46+
case Left(cmds) => cmds
47+
case Right(comps) => comps.map(_.label)
4648

4749
extension [A](state: State)
4850
infix def andThen(op: State ?=> A): A = op(using state)

0 commit comments

Comments
 (0)