Did you know you can
customize the sbt command prompt?
Neither did I
I have started getting
up to speed on using the sbt (Simple Build Tool). The de facto build tool for Scala projects…
although in truth I think I can be used to build other types of languages.
At any rate… I want to
learn sbt inside and out, every corner, every capability. To do this means I have a lot of reading,
watching, and poking to do.
This is one of the
Pokes.
Why?
Why would you want to
customize your prompt? Well for me it’s
the same reason I customize my Linux prompt… more Information. I use color to indicate where I am, local or
on a server, and what folder I am in.
In sbt I am not sure
what kind of information I am going to want to display. I do know I want to color it, if I can, so
that it will be obvious I am running sbt on a terminal.
Let me start with something
simple. Here is my very simple
build.sbt file
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state => " >> " }
|
There you go! It's as simple as that.
Complex
But of course I want
some more details in my output.
I want
- the name of the project
- Colors!
- Unicode Character in prompt
Name of the project
This one is pretty
easy. Here is my updated code.
name :="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
s"[${Project.extract(state).currentProject.id}]
>> "
}
|
Hey that worked… Kinda
I am still new to sbt I thought setting name would set my project name.
But currentProject.id is
"kitten" not "preowned-kittens"
If I run projectId within sbt I get
"preowned-kittens" the name I set.
So what is wrong with my prompt?
I still have a long way to go on learning sbt. I am guessing the state is not updated before
the shellPrompt is set?
Anyway I found a way around it here is my updated code.
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
s"[${Project.extract(state).getOpt(sbt.Keys.name).
getOrElse(Project.extract(state).currentProject.id)}] >> "
}
|
This will just grab the value of name and if it can't find
it will default the currentProject.id.
If I reload sbt…
I get what I want.
Seems like I should be able to do this simpler…
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state => s"[${name.value}] >>
"}
|
Ahh that is much simpler.
Let me reload.
Wahoo that worked.
But what do I get if I comment out the name variable?
//name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state => s"[${name.value}] >>
"}
|
Now reload
Good it goes to the default nothing special to do.
Colors
How do I get color text, and color background in the sbt
shellprompt.?
You can use linux bash prompt colors. Here is a good site that goes over that http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
[2]
Colors codes (for text)
Color
|
Code
|
Black
|
0;30
|
Blue
|
0;34
|
Green
|
0;32
|
Cyan
|
0;36
|
Red
|
0;31
|
Purple
|
0;35
|
Brown
|
0;33
|
Light Gray
|
0;37
|
Dark Gray
|
1;30
|
Light Blue
|
1;34
|
Light
Green
|
1;32
|
Light Cyan
|
1;36
|
Light Red
|
1;31
|
Light Purple
|
1;35
|
Yellow
|
1;33
|
White
|
1;37
|
Colors codes (for background)
Color
|
Code
|
Default
|
0
|
Black
|
40
|
Red
|
41
|
Green
|
42
|
Yellow
|
43
|
Blue
|
44
|
Magenta
|
45
|
Cyan
|
46
|
White
|
47
|
0 is unique as it sets you back to the default
Here is the format
\[\033[1;33m\]
This would give you Yellow Text
\[\033[42m\]
This would give you a Green background
Let me see if I can get that working.
Let me try the yellow prompt
first
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
s"\[\033[1;33m\]${name.value}] >> "
}
|
This causes a crash
Looking at some examples at https://groups.google.com/forum/#!topic/simple-build-tool/H7HcaYiv8FM
[3]
Looks like I can lose some of the formatting.
I only need this
\033[1;33m
name :="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
s"\033[1;33m[${name.value}] >> "
}
|
Hey a yellow prompt!
Now let me add a green background!
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
s"\033[1;33m\033[42m[${name.value}] >> "
}
|
Now I have a green background.
The only problem with this is the green background
continues.
Lucky for us there is a reset button you can do on the
command line background
\033[0m
name :="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
s"\033[1;33m\033[42m[${name.value}]\033[0m >> "
}
|
It resets the background and the text color.
These are the colors I am used to. Turns out there are some more you can
use. I found this post http://unix.stackexchange.com/questions/124407/what-color-codes-can-i-use-in-my-ps1-prompt
[5]
Create and run this script to get a list of color numbers
that work on your system.
#!/bin/bash
color=16;
while [ $color -lt 245 ]; do
echo -e "$color:
\\033[38;5;${color}mhello\\033[48;5;${color}mworld\\033[0m"
((color++));
done
|
Or here is a more simple one liner you can run on the
command line.
>
for color in {0..255}; do echo -e
"$color:\\033[38;5;${color}mhello\\033[48;5;${color}mworld\\033[0m";
done
|
You should get something like this. The first 0-15 seem to be a default
colors. If you look further down you can
see shades.
So how do you use these colors?
For text color use 38;5;(color number)
\033[38;5;208m
For background use 48;5;(color number)
\033[48;5;208m
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
s"\033[38;5;18m\033[48;5;171m[${name.value}]\033[0m >>
"
}
|
Pink and Blue.
Let me clean up my code a little bit.
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
def textColor(color:
Int) = { s"\033[38;5;${color}m" }
def
backgroundColor(color:Int) = { s"\033[48;5;${color}m" }
def reset = { s"\033[0m" }
def formatText(str:
String)(txtColor: Int, backColor: Int) = {
s"${textColor(txtColor)}${backgroundColor(backColor)}${str}${reset}"
}
val white = 15
val orange = 166
formatText(s"[${name.value}]")(white, orange) + "
>> "
}
|
With this I get
Getting closer to what I want.
Let me fiddle a little more.
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
def textColor(color:
Int) = {
s"\033[38;5;${color}m" }
def backgroundColor(color:Int)
= { s"\033[48;5;${color}m" }
def reset = {
s"\033[0m" }
def formatText(str:
String)(txtColor: Int, backColor: Int) = {
s"${textColor(txtColor)}${backgroundColor(backColor)}${str}${reset}"
}
val white = 15
val orange = 166
formatText(s"[${name.value}]")(white, orange) + "\n > > > "
}
|
Getting closer to what I want
Let me fiddle a little more…
Let me fiddle a little more…
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
def textColor(color:
Int) = { s"\033[38;5;${color}m" }
def
backgroundColor(color:Int) = { s"\033[48;5;${color}m" }
def reset = { s"\033[0m" }
def formatText(str:
String)(txtColor: Int, backColor: Int) = {
s"${textColor(txtColor)}${backgroundColor(backColor)}${str}${reset}"
}
val red
= 1
val green
= 2
val yellow = 11
val white = 15
val black
= 16
val orange = 166
formatText(s"[${name.value}]")(white, orange) +
"\n " +
formatText("> ")(green,
black) +
formatText("> ")(yellow,
black) +
formatText("> ")(red, black)
}
|
Getting closer to what I want.
Last step for what I want involves Unicode characters.
Unicode Characters
I use Unicode Character \u276f on my command line. In fact to get it working I had to tweak a
font on my machine see http://www.whiteboardcoder.com/2015/02/snazzier-command-line.html
[5] for more information on that.
Now if I edit my code to
formatText(s"[${name.value}]")(white, orange) +
"\n " +
formatText("\u276f ")(green,
black) +
formatText("\u276f ")(yellow,
black) +
formatText("\u276f ")(red,
black)
}
|
I get this ? ? ?
..... why?
The font I am using has this Unicode character defined so
what is going on?
After doing some research I think I found out it is a Java
thing….
But, it's a thing that can be fixed.
I found this post that went over the subject http://stackoverflow.com/questions/1948044/printing-unicode-from-scala-interpreter
[6]
Looks like the simplest way to fix this problem is to set an
environment variable.
On Cygwin I edited ~/.bash_profile
>
vi ~/.bash_profile
|
Adding a JAVA_OPTS to the end.
#Scala/Java command line will now output unicode characters
correctly
export JAVA_OPTS="-Dfile.encoding=UTF-8"
|
Now reload the .bash_profile
> source
~/.bash_profile
|
Now try it.
Unicode!!
But I have an extra space between them I do not want.
Perfect
Here is my final code (thus far).
name
:="preowned-kittens"
version := "1.0"
shellPrompt := { state =>
def textColor(color:
Int) = {
s"\033[38;5;${color}m" }
def
backgroundColor(color:Int) = { s"\033[48;5;${color}m" }
def reset = {
s"\033[0m" }
def formatText(str:
String)(txtColor: Int, backColor: Int) = {
s"${textColor(txtColor)}${backgroundColor(backColor)}${str}${reset}"
}
val red = 1
val green = 2
val yellow = 11
val white = 15
val black = 16
val orange = 166
formatText(s"[${name.value}]")(white, orange) +
"\n " +
formatText("\u276f")(green, black) +
formatText("\u276f")(yellow, black) +
formatText("\u276f
")(red, black)
}
|
Global Configurations
Shell Prompts can get pretty personal. Having the shell prompt for sbt defined in
the build.sbt file may not be the right place to put it. It may be… it depends on your team dynamic.
What do you do to allow people to do their own personal
shellPrompts?
Any settings in the ~/.sbt/0.13/global.sbt will be applied
to all projects. (I think it used to be ~/.sbt/global.sbt but now that we only
have a guarantee of binary compatibility between major versions it makes since
to include the 0.13 folder)
Normally you would create the global.sbt file in
~/.sbt/0.13/ , but my set up in Cygwin
is not normal. (In other words I tried
and it did not work)
I found this page http://www.scala-sbt.org/0.13.5/docs/Launcher/Configuration.html
[8]
The .sbt folder needs to be in your user.home directory
according to JAVA. How do you check
this? Well start up a scala REPL and run
this command.
>
scala
> println(System.getProperty("user.home"))
|
There is my home directory.
To make my life easier I am going to make a symlink from my
Cygwin home directory, .sbt folder, to
here.
> ln
-fs /cygdrive/c/Users/patman/.sbt ~/.sbt
|
Now I can create the ~/.sbt/0.13/global.sbt file
> vi
~/.sbt/0.13/global.sbt
|
And place the
following in it.
shellPrompt := { state =>
def textColor(color:
Int) = {
s"\033[38;5;${color}m" }
def
backgroundColor(color:Int) = { s"\033[48;5;${color}m" }
def reset = {
s"\033[0m" }
def formatText(str:
String)(txtColor: Int, backColor: Int) = {
s"${textColor(txtColor)}${backgroundColor(backColor)}${str}${reset}"
}
val red = 1
val green = 2
val yellow = 11
val white = 15
val black = 16
val orange = 166
formatText(s"[${name.value}]")(white, orange) +
"\n " +
formatText("\u276f")(green,
black) +
formatText("\u276f")(yellow, black) +
formatText("\u276f
")(red, black)
}
|
Run reload in sbt and….
It works!
Here is my build.sbt
name
:="preowned-kittens"
version := "1.0"
|
Now I don't have to impose my command line preferences on
the rest of the team.
Future
I am not done with my command line the part I highlight in
orange should contain more information.
At this point I do not know what information I may want to add there.
Git sha?
Last successful Jenkins build?
Current Sensu alerts?
It's Scala I could put lots of interesting information here…
References
[1] sbt Global settings
Accessed 03/2016
[2] Bash Colours
Accessed 03/2016
[3] [sbt] How to
color ShellPrompt
Accessed 03/2016
[4] What color
codes can I use in my PS1 prompt?
Accessed 03/2016
[5] Snazzier command line prompt
Accessed
03/2016
[6] Printing Unicode from Scala interpreter
Accessed
03/2016
[7] Printing Unicode from Scala interpreter
Accessed
03/2016
[8] Sbt Launcher Configuration
Accessed
03/2016
Hi Patrick. Nice post.
ReplyDeleteDid you know that there is a plugin that comes pretty close to what you describe?
Check out my sbt-prompt plugin: https://github.com/agemooij/sbt-prompt/
All comments, issues and pull requests are very welcome!
Cool I will have to check it out!
DeleteI am an SBT newb so I have not yet even fiddled with plugins yet.
But my plan is to dive deep into sbt and figure it all out over the next year+ (well at least enough to be good at it :) )