summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2012-02-17 15:20:55 -0600
committerJesse Luehrs <doy@tozt.net>2012-02-17 17:29:55 -0600
commit5aca244430309339943d4bd53936aa18e31493e7 (patch)
tree254c22ad580672ad86e7e7e801905b72b933d123
parentff7ccd5e5f05a666e6fd7c424db76d1765fe3270 (diff)
downloadconf-5aca244430309339943d4bd53936aa18e31493e7.tar.gz
conf-5aca244430309339943d4bd53936aa18e31493e7.zip
rewrite my bash prompt in perl
-rw-r--r--bashrc130
-rwxr-xr-xbin/fancy-prompt323
2 files changed, 324 insertions, 129 deletions
diff --git a/bashrc b/bashrc
index f7726e1..b11e20d 100644
--- a/bashrc
+++ b/bashrc
@@ -205,135 +205,7 @@ else
export PROMPT_COMMAND="${PROMPT_COMMAND};history -a"
fi
# prompt {{{
-function _set_error {
- __error=$(printf %03d $?)
- if [[ "x${__error//0/}" != "x" ]]; then
- __error_color=$HIRED
- else
- __error_color=$NORM
- fi
-}
-function _set_vcs {
- local vcs
- local vcs_dirty
- local vcs_branch
- local vcs_dir
- local vcs_state
- local vcs_local_commits
- __vcs=''
- function _find_upwards {
- local pwd
- pwd=$(pwd)
- while [[ -n "$pwd" ]]; do
- if [[ -d "$pwd/$1" ]]; then
- return 0
- fi
- pwd=${pwd%/*}
- done
- return 1
- }
- function _find_svn {
- [[ -d '.svn' ]] || return 1
- vcs='svn'
- }
- function _find_darcs {
- if _find_upwards '_darcs'; then
- vcs='darcs'
- return 0
- fi
- return 1
- }
- function _find_git {
- if _find_upwards '.git'; then
- vcs='git'
- return 0
- fi
- return 1
- }
-
- _find_svn || _find_darcs || _find_git
-
- case "$vcs" in
- svn)
- vcs_dirty=$([[ -n "$(svn status | grep -v '^?')" ]]; echo $?)
- if [[ "$vcs_dirty" -eq 0 ]]; then
- vcs_dirty='*'
- else
- vcs_dirty=''
- fi
- vcs_branch=
- ;;
- darcs)
- vcs_dirty=$(darcs whatsnew > /dev/null 2>&1; echo $?)
- if [[ "$vcs_dirty" -eq 0 ]]; then
- vcs_dirty='*'
- else
- vcs_dirty=''
- fi
- vcs_branch=
- ;;
- git)
- local git_dir
- local git_base
- git_dir=$(git rev-parse --git-dir)
- vcs_branch=$(git symbolic-ref -q HEAD 2>/dev/null)
- vcs_dirty=''
- if git diff --no-ext-diff --ignore-submodules --quiet --exit-code; then
- :
- else
- vcs_dirty="${vcs_dirty}*"
- fi
- if git diff-index --cached --quiet --ignore-submodules HEAD; then
- :
- else
- vcs_dirty="${vcs_dirty}+"
- fi
- if [[ -z "${vcs_branch}" ]]; then
- vcs_branch=$(git rev-parse --short HEAD)
- else
- vcs_branch=${vcs_branch#refs/heads/}
- if git show-ref -q --verify "refs/remotes/origin/$vcs_branch"; then
- git_base=$(git merge-base HEAD origin/$vcs_branch)
- if [[ "${git_base}" == "$(git rev-parse HEAD)" ]]; then
- vcs_local_commits="-$(git rev-list HEAD..origin/$vcs_branch | wc -l)"
- elif [[ "${git_base}" == "$(git rev-parse origin/$vcs_branch)" ]]; then
- vcs_local_commits="+$(git rev-list origin/${vcs_branch}..HEAD | wc -l)"
- else
- vcs_local_commits="+$(git rev-list ${git_base}..HEAD | wc -l)-$(git rev-list ${git_base}..origin/$vcs_branch | wc -l)"
- fi
- fi
- fi
- if [[ -e "${git_dir}/MERGE_HEAD" ]]; then
- vcs_state='merge'
- fi
- if [[ -d "${git_dir}/rebase-apply" || -d "${git_dir}/rebase-merge" ]]; then
- vcs_state='rebase'
- fi
- if [[ -e "${git_dir}/BISECT_START" ]]; then
- vcs_state='bisect'
- fi
- if [[ "x$vcs_branch" == "xmaster" ]]; then
- vcs_branch=''
- fi
- ;;
- *) return 0 ;;
- esac
- if [[ -n "$vcs_branch" ]]; then
- vcs_branch=":$vcs_branch"
- fi
- if [[ -n "$vcs_state" ]]; then
- vcs_state="(${vcs_state:0:1})"
- fi
- if [[ "$vcs_local_commits" == "-0" ]]; then
- vcs_local_commits=''
- fi
- if [[ -n "$vcs_local_commits" ]]; then
- vcs_local_commits=":$vcs_local_commits"
- fi
- __vcs="(${vcs:0:1}${vcs_dirty}${vcs_branch}${vcs_local_commits}${vcs_state})"
-}
-export PROMPT_COMMAND="_set_error;_set_vcs;$PROMPT_COMMAND"
-export PS1="\[\$__error_color\]\$__error \[${HIYELLOW}\][\t] \[${HIGREEN}\]\u@\h \[${HIBLUE}\]\W\[${CYAN}\]\$__vcs \[${HIBLUE}\]\\$\[${NORM}\] "
+export PROMPT_COMMAND="__err=\$?;$PROMPT_COMMAND;PS1=\"\$(fancy-prompt "\$__err")\""
# }}}
# set the correct perl {{{
if type -a perlbrew > /dev/null 2>&1; then
diff --git a/bin/fancy-prompt b/bin/fancy-prompt
new file mode 100755
index 0000000..d9f9809
--- /dev/null
+++ b/bin/fancy-prompt
@@ -0,0 +1,323 @@
+#!/usr/bin/env perl
+# keep these commented out in actual use, for speed (saves ~2ms)
+# use strict;
+# use warnings;
+
+# collect information
+chomp(my $hostname = `hostname`);
+my $cols = `tput cols`;
+my $pwd = $ENV{PWD};
+my $user = $ENV{USER};
+my $root = ($> == 0);
+(my $time = localtime) =~ s/.*(\d\d:\d\d:\d\d).*/$1/;
+my $err = $ARGV[0] || 0; # bash's $? is passed on the command line
+my $battery = battery();
+my $charging = charging();
+my $in_tmux = exists $ENV{TMUX};
+my $in_ssh = exists $ENV{SSH_CONNECTION};
+my $vcs = vcs();
+
+# initialize colors
+my $tput = join("\n", map { "setf $_" } 0..15);
+my @colors = qw(
+ black blue green cyan red magenta brown grey
+ darkgrey lightred lightgreen yellow lightblue lightmagenta lightcyan white
+);
+my %colors;
+my $termcolors = `echo "$tput" | tput -S`;
+if (!$?) {
+ my @termcolors = split /(?=\e)/, $termcolors;
+ $colors{$colors[$_]} = $termcolors[$_] for 0..$#colors;
+ $colors{reset} = `tput sgr0`; # this can be multiple escapes
+}
+else {
+ # hardcoded fallback, if terminfo isn't available
+ %colors = (
+ reset => "\e[m",
+
+ black => "\e[0;30m",
+ blue => "\e[0;34m",
+ green => "\e[0;32m",
+ cyan => "\e[0;36m",
+ red => "\e[0;31m",
+ magenta => "\e[0;35m",
+ brown => "\e[0;33m",
+ grey => "\e[0;37m",
+
+ darkgrey => "\e[1;30m",
+ lightred => "\e[1;31m",
+ lightgreen => "\e[1;32m",
+ yellow => "\e[1;33m",
+ lightblue => "\e[1;34m",
+ lightmagenta => "\e[1;35m",
+ lightcyan => "\e[1;36m",
+ white => "\e[1;37m",
+ );
+}
+
+my %colornames = (
+ bishamon => 'blue',
+ erim => 'white',
+ magus => 'darkgrey',
+ tozt => 'yellow',
+ xtahua => 'lightmagenta',
+ zaon => 'lightgreen',
+
+ error => 'red',
+
+ user => 'lightblue',
+ root => 'lightred',
+
+ path => 'blue',
+ ropath => 'red',
+
+ vcs => 'cyan',
+
+ battwarn => 'brown',
+ battcrit => 'red',
+ battemerg => 'lightred',
+
+ time => 'blue',
+
+ background => 'darkgrey',
+ reset => 'reset',
+);
+$colornames{hostname} = $colornames{$hostname};
+$colornames{user} = $colornames{root} if $root;
+$colornames{path} = $colornames{ropath} unless -w $pwd;
+$colornames{error} = $colornames{background} unless $err;
+$colornames{battery} = $battery > 40 ? $colornames{background}
+ : $battery > 15 ? $colornames{battwarn}
+ : $battery > 5 ? $colornames{battcrit}
+ : $colornames{battemerg};
+sub color { $colors{$colornames{$_[0]}} }
+
+# define some display things
+my $border = '-';
+my $meter = '=';
+my $empty_meter = '-';
+my $meter_end = $charging ? '<' : '>';
+my $prompt = $root ? '#' : '$';
+my $meter_length = 10;
+my $maxvcslen = 20; # at least 17
+
+# snippets
+my $nested = '';
+# not sure how useful this really is, let's leave it off for now
+# if ($in_tmux || $in_ssh) {
+# $nested = '(' . ($in_tmux ? 'T' : '') . ($in_ssh ? 'S' : '') . ')';
+# }
+
+# build the pieces of the prompt
+my $timestr = wrap($time, '[]', color('time'));
+
+my $errstr = color('error') . sprintf('%03d', $err);
+
+my $promptstr = color('user') . $prompt . color('reset');
+
+my $full_length = int($battery / 100 * $meter_length);
+my $battstr = wrap(($empty_meter x ($meter_length - $full_length))
+ . $meter_end . ($meter x ($full_length - 1)),
+ '{}',
+ color('battery'));;
+
+my $trimmed_vcs = trimvcs($vcs, $maxvcslen);
+my $vcslen = $trimmed_vcs ? length($trimmed_vcs) + 1 : 0;
+my $maxpathlen = $cols
+ - 1
+ - $vcslen - 2
+ - 1
+ - 1
+ - $meter_length - 2
+ - 1
+ - length($user) - length($hostname) - length($nested) - 1
+ - 1
+ - length($time) - 2
+ - 1;
+
+my $trimmed_pwd = trimpath($pwd, $maxpathlen - 1);
+my $path_info = $trimmed_pwd
+ . ($trimmed_vcs ? (color('background') . '|') : '')
+ . color('vcs') . $trimmed_vcs;
+my $pathstr = wrap($path_info, '()', color('path'))
+ . ' ' . color('background') . ($border x ($maxpathlen - length($trimmed_pwd)));
+
+my $locstr = color('user') . $user
+ . color('background') . '@'
+ . color('hostname') . $hostname
+ . color('background') . $nested;
+
+# assemble the prompt
+print join(' ', '', $pathstr, $battstr, $locstr, $timestr, ''), "\n";
+print join(' ', $errstr, $promptstr, '');
+
+sub battery {
+ my $now = slurp('/sys/class/power_supply/BAT0/charge_now');
+ my $full = slurp('/sys/class/power_supply/BAT0/charge_full');
+ return int(100 * $now / $full);
+}
+
+sub charging {
+ my $ac = slurp('/sys/class/power_supply/AC/online');
+ chomp($ac);
+ return $ac;
+}
+
+sub slurp {
+ open my $fh, '<', $_[0];
+ local $/;
+ <$fh>;
+}
+
+sub wrap {
+ my ($str, $wrapper, $color) = @_;
+ $wrapper = $wrapper x 2 if length($wrapper) == 1;
+ return color('hostname') . substr($wrapper, 0, 1)
+ . $color . $str
+ . color('hostname') . substr($wrapper, 1, 1);
+}
+
+sub trimpath {
+ my ($path, $max) = @_;
+
+ $path =~ s/^$ENV{HOME}/~/;
+
+ while (length($path) > $max) {
+ $path =~ s|([^/])[^/]+/|$1/|
+ or last;
+ }
+
+ if (length($path) > $max) {
+ $path = substr($path, 0, $max - 6) . '...' . substr($path, -3);
+ }
+
+ return $path;
+}
+
+sub trimvcs {
+ my ($vcsinfo, $max) = @_;
+ return $vcsinfo if length($vcsinfo) <= $max;
+ my @parts = split /:/, $vcsinfo;
+ $max -= length($parts[0]) + 1;
+ $max -= length($parts[2]) + 1 if $parts[2];
+ $parts[1] = substr($parts[1], 0, $max - 6) . '...' . substr($parts[1], -3);
+ return join(':', @parts);
+}
+
+sub vcs {
+ my ($vcs, $vcsdir) = find_vcs();
+ return '' unless $vcs;
+ return git_info($vcsdir) if $vcs eq 'git';
+ return svn_info($vcsdir) if $vcs eq 'svn';
+ return darcs_info($vcsdir) if $vcs eq 'darcs';
+ return;
+}
+
+sub find_upwards {
+ my ($vcsdir) = @_;
+
+ my $path = $pwd;
+ while ($path) {
+ if (-d "$path/$vcsdir") {
+ return "$path/$vcsdir";
+ }
+ $path =~ s{/.*?$}{};
+ }
+
+ return;
+}
+
+sub find_svn { -d "$pwd/.svn" ? "$pwd/.svn" : undef }
+sub find_git {
+ chomp(my $gitdir = `git rev-parse --git-dir 2>/dev/null`);
+ return $gitdir;
+}
+sub find_darcs { find_upwards('_darcs') }
+
+sub find_vcs {
+ if (my $git = find_git()) {
+ return ('git', $git);
+ }
+ elsif (my $svn = find_svn()) {
+ return ('svn', $svn);
+ }
+ elsif (my $darcs = find_darcs()) {
+ return ('darcs', $darcs);
+ }
+ return;
+}
+
+sub svn_info {
+ my $dirty = '';
+ if (!system(q[svn status | grep -qv '^?'])) {
+ $dirty .= '*';
+ }
+
+ return 's' . $dirty;
+}
+
+sub darcs_info {
+ my $dirty = '';
+ if (!system(q[darcs whatsnew > /dev/null 2>&1])) {
+ $dirty .= '*';
+ }
+
+ return 'd' . $dirty;
+}
+
+sub git_info {
+ my ($git_dir) = @_;
+
+ my $dirty = '';
+ if (system(q[git diff --no-ext-diff --ignore-submodules --quiet --exit-code])) {
+ $dirty .= '*';
+ }
+ if (system(q[git diff-index --cached --quiet --ignore-submodules HEAD])) {
+ $dirty .= '+';
+ }
+
+ chomp(my $branch = `git symbolic-ref -q HEAD 2>/dev/null`
+ || `git rev-parse --short HEAD`);
+ $branch =~ s{^refs/heads/}{};
+
+ my $local_commits = '';
+ system(qq[git show-ref -q --verify "refs/remotes/origin/$branch"]);
+ if (!$?) {
+ chomp(my $base = `git merge-base HEAD origin/$branch`);
+ chomp(my $head = `git rev-parse HEAD`);
+ chomp(my $remote = `git rev-parse origin/$branch`);
+ if ($base eq $head) {
+ my @revs = `git rev-list HEAD..origin/$branch`;
+ $local_commits = '-' . scalar(@revs);
+ }
+ elsif ($base eq $remote) {
+ my @revs = `git rev-list origin/$branch..HEAD`;
+ $local_commits = '+' . scalar(@revs);
+ }
+ else {
+ my @head_revs = `git rev-list $base..HEAD`;
+ my @remote_revs = `git rev-list $base..origin/$branch`;
+ $local_commits = '+' . scalar(@head_revs)
+ . '-' . scalar(@remote_revs);
+ }
+ }
+
+ $local_commits = '' if $local_commits eq '-0';
+ $local_commits = ':' . $local_commits if $local_commits;
+
+ my $state = '';
+ if (-e "$git_dir/MERGE_HEAD") {
+ $state = 'm';
+ }
+ elsif (-d "$git_dir/rebase-apply" || -d "$git_dir/rebase-merge") {
+ $state = 'r';
+ }
+ elsif (-e "$git_dir/BISECT_START") {
+ $state = 'b';
+ }
+
+ $branch = '' if $branch eq 'master';
+ $branch = ':' . $branch if $branch;
+
+ return 'g' . $dirty . $branch . $local_commits . $state;
+}