Skip to content

Commit

Permalink
Merge pull request #9521 from jxnu-liguobin/fix-12264
Browse files Browse the repository at this point in the history
REPL: improve tab-completion of `:`-prefixed commands (fixes scala/bug#12264)
  • Loading branch information
SethTisue committed Mar 12, 2021
2 parents 3540cf2 + a2e187e commit 93ec1f0
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 31 deletions.
18 changes: 9 additions & 9 deletions src/repl-frontend/scala/tools/nsc/interpreter/jline/Reader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
package scala.tools.nsc.interpreter
package jline

import java.util.{List => JList}

import org.jline.reader.{Candidate, Completer, CompletingParsedLine, EOFError, EndOfFileException, History, LineReader, ParsedLine, Parser, SyntaxError, UserInterruptException}
import org.jline.reader.Parser.ParseContext
import org.jline.reader.impl.{DefaultParser, LineReaderImpl}
import org.jline.reader._
import org.jline.terminal.Terminal

import shell.{Accumulator, ShellConfig}
import Parser.ParseContext
import java.util.{List => JList}
import scala.tools.nsc.interpreter.shell.{Accumulator, ShellConfig}

/** A Reader that delegates to JLine3.
*/
Expand Down Expand Up @@ -109,7 +108,8 @@ object Reader {
}
}
def backupHistory(): Unit = {
import java.nio.file.{Files, Paths, StandardCopyOption}, StandardCopyOption.REPLACE_EXISTING
import java.nio.file.{Files, Paths, StandardCopyOption}
import StandardCopyOption.REPLACE_EXISTING
val hf = Paths.get(config.historyFile)
val bk = Paths.get(config.historyFile + ".bk")
Files.move(/*source =*/ hf, /*target =*/ bk, REPLACE_EXISTING)
Expand Down Expand Up @@ -229,8 +229,8 @@ class Completion(delegate: shell.Completion) extends shell.Completion with Compl

// JLine Completer
def complete(lineReader: LineReader, parsedLine: ParsedLine, newCandidates: JList[Candidate]): Unit = {
def candidateForResult(cc: CompletionCandidate): Candidate = {
val value = cc.defString
def candidateForResult(line: String, cc: CompletionCandidate): Candidate = {
val value = if (line.startsWith(":")) ":" + cc.defString else cc.defString
val displayed = cc.defString + (cc.arity match {
case CompletionCandidate.Nullary => ""
case CompletionCandidate.Nilary => "()"
Expand Down Expand Up @@ -263,7 +263,7 @@ class Completion(delegate: shell.Completion) extends shell.Completion with Compl
// normal completion
case _ =>
for (cc <- result.candidates)
newCandidates.add(candidateForResult(cc))
newCandidates.add(candidateForResult(result.line, cc))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ object NoCompletion extends Completion {
def complete(buffer: String, cursor: Int) = NoCompletions
}

case class CompletionResult(cursor: Int, candidates: List[CompletionCandidate]) {
case class CompletionResult(line: String, cursor: Int, candidates: List[CompletionCandidate]) {
final def orElse(other: => CompletionResult): CompletionResult =
if (candidates.nonEmpty) this else other
}
object CompletionResult {
val empty: CompletionResult = NoCompletions
}
object NoCompletions extends CompletionResult(-1, Nil)
object NoCompletions extends CompletionResult("", -1, Nil)

case class MultiCompletion(underlying: Completion*) extends Completion {
override def complete(buffer: String, cursor: Int) =
underlying.foldLeft(CompletionResult.empty)((r,c) => r.orElse(c.complete(buffer, cursor)))
underlying.foldLeft(CompletionResult.empty)((r, c) => r.orElse(c.complete(buffer, cursor)))
}
12 changes: 6 additions & 6 deletions src/repl-frontend/scala/tools/nsc/interpreter/shell/ILoop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,23 +223,23 @@ class ILoop(config: ShellConfig, inOverride: BufferedReader = null,
val emptyWord = """(\s+)$""".r.unanchored
val directorily = """(\S*/)$""".r.unanchored
val trailingWord = """(\S+)$""".r.unanchored
def listed(i: Int, dir: Option[Path]) =
def listed(buffer: String, i: Int, dir: Option[Path]) =
dir.filter(_.isDirectory)
.map(d => CompletionResult(i, d.toDirectory.list.map(x => CompletionCandidate(x.name)).toList))
.map(d => CompletionResult(buffer, i, d.toDirectory.list.map(x => CompletionCandidate(x.name)).toList))
.getOrElse(NoCompletions)
def listedIn(dir: Directory, name: String) = dir.list.filter(_.name.startsWith(name)).map(_.name).toList
def complete(buffer: String, cursor: Int): CompletionResult =
buffer.substring(0, cursor) match {
case emptyWord(s) => listed(cursor, Directory.Current)
case directorily(s) => listed(cursor, Option(Path(s)))
case emptyWord(s) => listed(buffer, cursor, Directory.Current)
case directorily(s) => listed(buffer, cursor, Option(Path(s)))
case trailingWord(s) =>
val f = File(s)
val (i, maybes) =
if (f.isFile) (cursor - s.length, List(f.toAbsolute.path))
else if (f.isDirectory) (cursor - s.length, List(s"${f.toAbsolute.path}/"))
else if (f.parent.exists) (cursor - f.name.length, listedIn(f.parent.toDirectory, f.name))
else (-1, Nil)
if (maybes.isEmpty) NoCompletions else CompletionResult(i, maybes.map(CompletionCandidate(_)))
if (maybes.isEmpty) NoCompletions else CompletionResult(buffer, i, maybes.map(CompletionCandidate(_)))
case _ => NoCompletions
}
}
Expand All @@ -253,7 +253,7 @@ class ILoop(config: ShellConfig, inOverride: BufferedReader = null,
val maybes = intp.visibleSettings.filter(_.name.startsWith(s)).map(_.name)
.filterNot(cond(_) { case "-"|"-X"|"-Y" => true }).sorted
if (maybes.isEmpty) NoCompletions
else CompletionResult(cursor - s.length, maybes.map(CompletionCandidate(_)))
else CompletionResult(buffer, cursor - s.length, maybes.map(CompletionCandidate(_)))
case _ => NoCompletions
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ trait LoopCommands {
val completion = if (cmd.isInstanceOf[NullaryCmd] || cursor < line.length) cmd.name else cmd.name + " "
new Completion {
def complete(buffer: String, cursor: Int) =
CompletionResult(cursor = 1, List(CompletionCandidate(completion)))
CompletionResult(buffer, cursor = 1, List(CompletionCandidate(completion)))
}
case cmd :: rest =>
new Completion {
def complete(buffer: String, cursor: Int) =
CompletionResult(cursor = 1, cmds.map(cmd => CompletionCandidate(cmd.name)))
CompletionResult(buffer, cursor = 1, cmds.map(cmd => CompletionCandidate(cmd.name)))
}
}
case _ => NoCompletion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ package scala.tools.nsc.interpreter
package shell

import scala.util.control.NonFatal
import scala.tools.nsc.interpreter.Repl
import scala.tools.nsc.interpreter.Naming

/** Completion for the REPL.
*/
Expand Down Expand Up @@ -50,17 +48,17 @@ class ReplCompletion(intp: Repl, val accumulator: Accumulator = new Accumulator)
case Left(_) => NoCompletions
case Right(result) => try {
buf match {
case slashPrint() if cursor == buf.length =>
CompletionResult(cursor, CompletionCandidate.fromStrings("" :: Naming.unmangle(result.print) :: Nil))
case slashPrintRaw() if cursor == buf.length =>
CompletionResult(cursor, CompletionCandidate.fromStrings("" :: result.print :: Nil))
case slashPrint() if cursor == buf.length =>
CompletionResult(buf, cursor, CompletionCandidate.fromStrings("" :: Naming.unmangle(result.print) :: Nil))
case slashPrintRaw() if cursor == buf.length =>
CompletionResult(buf, cursor, CompletionCandidate.fromStrings("" :: result.print :: Nil))
case slashTypeAt(start, end) if cursor == buf.length =>
CompletionResult(cursor, CompletionCandidate.fromStrings("" :: result.typeAt(start.toInt, end.toInt) :: Nil))
case _ =>
CompletionResult(buf, cursor, CompletionCandidate.fromStrings("" :: result.typeAt(start.toInt, end.toInt) :: Nil))
case _ =>
// under JLine 3, we no longer use the tabCount concept, so tabCount is always 1
// which always gives us all completions
val (c, r) = result.completionCandidates(tabCount = 1)
CompletionResult(c, r)
CompletionResult(buf, c, r)
}
} finally result.cleanup()
}
Expand Down
36 changes: 34 additions & 2 deletions test/junit/scala/tools/nsc/interpreter/CompletionTest.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package scala.tools.nsc.interpreter

import java.io.{PrintWriter, StringWriter}

import org.junit.Assert.{assertEquals, assertTrue}
import org.junit.Test

import java.io.{PrintWriter, StringWriter}
import scala.reflect.internal.util.{BatchSourceFile, SourceFile}
import scala.tools.nsc.Settings
import scala.tools.nsc.interpreter.shell._
Expand Down Expand Up @@ -36,6 +35,28 @@ class CompletionTest {
(completer, intp, acc)
}

private def commandInterpretLines(): (Completion, Repl, Accumulator) = {
val intp = newIMain()
class CommandMock extends LoopCommands {
override protected def echo(msg: String): Unit = ???
override protected def out: PrintWriter = ???
override def commands: List[LoopCommand] = {
val default = (string: String) => Result.default
List(
LoopCommand.cmd("paste", "[-raw] [path]", "enter paste mode or paste a file", default),
LoopCommand.cmd("paste", "[-raw] [path]", "enter paste mode or paste a file", default)// Other commands
)
}
}
val acc = new Accumulator
val shellCompletion = new Completion {
override def complete(buffer: String, cursor: Int) =
if (buffer.startsWith(":")) new CommandMock().colonCompletion(buffer, cursor).complete(buffer, cursor)
else NoCompletions
}
(shellCompletion, intp, acc)
}

implicit class BeforeAfterCompletion(completion: Completion) {
def complete(before: String, after: String = ""): CompletionResult =
completion.complete(before + after, before.length)
Expand Down Expand Up @@ -231,6 +252,17 @@ class CompletionTest {
assertTrue(candidates2.last.defString.contains("deprecated"))
}

@Test
def jline3Matcher(): Unit = {
val (completer, _, _) = commandInterpretLines()
val candidates1 = completer.complete(":p").candidates
assertEquals(2, candidates1.size)

// Save the line to the CompletionResult of the matcher, and select the command to match successfully.
val completionResult = completer.complete(":p")
assertEquals(completionResult.line, ":p")
}

@Test
def isNotDeprecated(): Unit = {
val (completer, _, _) = interpretLines(
Expand Down

0 comments on commit 93ec1f0

Please sign in to comment.