|
import sbt._ |
|
import Keys._ |
|
|
|
/* |
|
* Initial idea shamelessly stolen from the Play framework, all credits to them! |
|
* Heavily customized aftwards based on my lightly customized version of the |
|
* Oh-My-Zsh "agnoster" theme. |
|
* |
|
* This theme uses some special icon-like characters (the segment separator |
|
* and the Git branch icon) that are not supported by most fonts. In order |
|
* for this theme to render correctly, you will need a |
|
* [Powerline-patched font](https://github.com/Lokaltog/powerline-fonts). |
|
* |
|
* If you want to customize the colors used in this theme, have a look at |
|
* these handy references: |
|
* |
|
* - [Ansi escape code reference](http://misc.flogisoft.com/bash/tip_colors_and_formatting) |
|
* - [Graphical overview of the Ansi 256 color codes](http://www.calmar.ws/vim/color-output.png) |
|
* |
|
* Enjoy! |
|
* Age (@agemooij) |
|
*/ |
|
object ShellPrompt extends Plugin with Ansi256ColorSupport with PromptSegmentSupport with GitSupport { |
|
override def settings = Seq( |
|
shellPrompt := { state => |
|
val extracted = Project.extract(state) |
|
|
|
import extracted._ |
|
import com.typesafe.sbt.SbtGit._ |
|
|
|
val project = currentRef.project |
|
val root = rootProject(currentRef.build) |
|
val path = if (project == root) project else s"${root}/${project}" |
|
|
|
val branch = " " + extracted.get(GitKeys.gitCurrentBranch) |
|
|
|
prompt( |
|
List( |
|
PromptSegment("SBT", 26, 235), |
|
if (isGitDirty(state, extracted)) PromptSegment(branch, 214, 235) else PromptSegment(branch, 34, 235), |
|
PromptSegment(path, 235, 250) |
|
), |
|
"" |
|
) |
|
} |
|
) |
|
} |
|
|
|
|
|
trait GitSupport { |
|
import com.typesafe.sbt.SbtGit._ |
|
def isGitDirty(state: State, extracted: Extracted): Boolean = { |
|
val (_, runner) = extracted.runTask(GitKeys.gitRunner, state) |
|
val dir = extracted.get(baseDirectory) |
|
val result = runner("diff-index", "HEAD", "--")(dir, NoOpSbtLogger) |
|
|
|
!result.trim.isEmpty |
|
} |
|
} |
|
|
|
|
|
case class PromptSegment(text: String, bg: Int, fg: Int) |
|
|
|
|
|
trait PromptSegmentSupport extends Ansi256ColorSupport { |
|
def prompt(segments: Seq[PromptSegment], separator: String): String = { |
|
val combined = segments.foldLeft("") { (combined, segment) => |
|
if (combined.isEmpty) |
|
s"${combined}${bg(segment.bg)}${fg(segment.fg)} ${segment.text} ${fg(segment.bg)}" |
|
else |
|
s"${combined}${bg(segment.bg)}${separator}${fg(segment.fg)} ${segment.text} ${fg(segment.bg)}" |
|
} |
|
|
|
s"${combined}${defaultBg}${separator}${defaultFg} " |
|
} |
|
} |
|
|
|
trait AnsiColorSupport { |
|
val defaultFg = "\u001b[39m" |
|
val defaultBg = "\u001b[49m" |
|
val resetColors = defaultBg + defaultFg |
|
|
|
lazy val isANSISupported = { |
|
Option(System.getProperty("sbt.log.noformat")).map(_ != "true").orElse { |
|
Option(System.getProperty("os.name")) |
|
.map(_.toLowerCase) |
|
.filter(_.contains("windows")) |
|
.map(_ => false) |
|
}.getOrElse(true) |
|
} |
|
} |
|
|
|
trait Ansi256ColorSupport extends AnsiColorSupport { |
|
def fg(code: Int): String = { |
|
require(code >= 0 && code <= 255) |
|
s"\u001b[38;5;${code}m" |
|
} |
|
|
|
def bg(code: Int): String = { |
|
require(code >= 0 && code <= 255) |
|
s"\u001b[48;5;${code}m" |
|
} |
|
|
|
/** Little util for potential theme writers to get a quick overview of the supported ANSI256 color escape sequences. */ |
|
def printSupportedColors: Unit = { |
|
for (i <- 0 to 255) { |
|
println(s"${fg(i)} foreground (${i}) $defaultFg ${bg(i)} background (${i}) $defaultBg") |
|
} |
|
} |
|
} |
|
|
|
object NoOpSbtLogger extends Logger { |
|
def trace(t: => Throwable): Unit = {} |
|
def success(message: => String): Unit = {} |
|
def log(level: Level.Value, message: => String): Unit = {} |
|
} |