diff --git a/build.sbt b/build.sbt index 507498826..9a3265239 100644 --- a/build.sbt +++ b/build.sbt @@ -307,6 +307,7 @@ val completeProj = (project in file("internal") / "util-complete") name := "Completion", libraryDependencies += jline, libraryDependencies += jline3Reader, + libraryDependencies += jline3Builtins, mimaSettings, // Parser is used publicly, so we can't break bincompat. mimaBinaryIssueFilters := Seq( diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index 439d8de85..aa022d3bb 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -23,10 +23,15 @@ import org.jline.reader.{ } import org.jline.utils.ClosedException import sbt.internal.util.complete.Parser +import sbt.io.syntax._ import scala.annotation.tailrec import scala.concurrent.duration._ +import scala.util.control.NonFatal import java.nio.channels.ClosedByInterruptException +import java.net.MalformedURLException + +import org.jline.builtins.InputRC trait LineReader extends AutoCloseable { def readLine(prompt: String, mask: Option[Char] = None): Option[String] @@ -70,6 +75,29 @@ object LineReader { } } } + private[this] def inputrcFileUrl(): Option[URL] = { + // keep jline2 compatibility + // https://github.com/jline/jline2/blob/12b98d94589e3bd6a6/src/main/java/jline/console/ConsoleReader.java#L291-L306 + sys.props + .get("jline.inputrc") + .flatMap { path => + try { + Some(url(path)) + } catch { + case _: MalformedURLException => + Some(file(path).toURI.toURL) + } + } + .orElse { + sys.props.get("user.home").map { home => + val f = file(home) / ".inputrc" + (if (f.isFile) f else file("/etc/inputrc")).toURI.toURL + } + } + } + // cache on memory. + private[this] lazy val inputrcFileContents: Option[Array[Byte]] = + inputrcFileUrl().map(in => sbt.io.IO.readBytes(in.openStream())) def createReader( historyPath: Option[File], parser: Parser[_], @@ -81,6 +109,17 @@ object LineReader { override def readLine(prompt: String, mask: Option[Char]): Option[String] = { val term = JLine3(terminal) val reader = LineReaderBuilder.builder().terminal(term).completer(completer(parser)).build() + try { + inputrcFileContents.foreach { bytes => + InputRC.configure( + reader, + new ByteArrayInputStream(bytes) + ) + } + } catch { + case NonFatal(_) => + // ignore + } historyPath.foreach(f => reader.setVariable(JLineReader.HISTORY_FILE, f)) try terminal.withRawInput { Option(mask.map(reader.readLine(prompt, _)).getOrElse(reader.readLine(prompt))) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 3a0d2322e..e3d63cb1f 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -89,6 +89,7 @@ object Dependencies { val jline3Jansi = "org.jline" % "jline-terminal-jansi" % jline3Version val jline3JNA = "org.jline" % "jline-terminal-jna" % jline3Version val jline3Reader = "org.jline" % "jline-reader" % jline3Version + val jline3Builtins = "org.jline" % "jline-builtins" % jline3Version val jansi = "org.fusesource.jansi" % "jansi" % "1.18" val scalatest = "org.scalatest" %% "scalatest" % "3.0.8" val scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.0"