summaryrefslogtreecommitdiffstats
path: root/bin/fancy-prompt
diff options
context:
space:
mode:
Diffstat (limited to 'bin/fancy-prompt')
-rwxr-xr-xbin/fancy-prompt323
1 files changed, 323 insertions, 0 deletions
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;
+}