307 lines
8.6 KiB
Bash
307 lines
8.6 KiB
Bash
prompt_fresh_help() {
|
||
echo "\
|
||
options:
|
||
-a enable almostontop like behaviour
|
||
-r recompile git-prompt executable"
|
||
}
|
||
|
||
prompt_fresh_setup() {
|
||
# Parse options
|
||
local almostontop=0
|
||
local recompile=0
|
||
while getopts 'ar' opt; do
|
||
case $opt in
|
||
a) local almostontop=1 ;;
|
||
r) local recompile=1 ;;
|
||
*) prompt -h fresh; return 1 ;;
|
||
esac
|
||
done
|
||
|
||
autoload -U add-zsh-hook
|
||
|
||
# Hook to print the first line of the "two line" prompt, this line is not
|
||
# actually part of the prompt so does not get redrawn, this is preferable
|
||
# since sometimes lines before the prompt can disappear.
|
||
add-zsh-hook precmd fresh_line_one
|
||
|
||
if [ $almostontop -eq 1 ]; then
|
||
# Hook to clear the screen then prints the prompt with previous command at
|
||
# the top of the screen.
|
||
add-zsh-hook preexec fresh_almostontop
|
||
else
|
||
add-zsh-hook -d preexec fresh_almostontop
|
||
fi
|
||
|
||
if [ $recompile -eq 1 ]; then
|
||
[ -f ~/.cache/zsh/git-prompt ] && rm ~/.cache/zsh/git-prompt
|
||
fi
|
||
fresh_compile_git_prompt
|
||
|
||
if [ "$USER" = "root" ]; then
|
||
local user="%{%F{1}%}%n%{%f%}"
|
||
else
|
||
local user="%{%F{35}%}%n%{%f%}"
|
||
fi
|
||
|
||
local userhost=$USER
|
||
if [ "$SSH_CONNECTION" != "" ]; then
|
||
local user="$user@%{%F{244}%}%M%{%f%}"
|
||
local userhost="$userhost@`hostname`"
|
||
fi
|
||
|
||
PS1="«$user» "
|
||
PS2="«${(l:${#userhost}:: :)}» "
|
||
|
||
prompt_opts=(percent sp subst)
|
||
}
|
||
|
||
prompt_cleanup() {
|
||
[ -f ~/.cache/zsh/git-prompt ] && rm ~/.cache/zsh/git-prompt
|
||
}
|
||
|
||
fresh_line_one() {
|
||
# First get the last commands exit code before doing anything
|
||
local exit_code=$?
|
||
|
||
# Construct the time and directory portions of the prompt
|
||
local time_stamp="%{%F{244}%}%D{%H:%M:%S}%{%f%}"
|
||
[[ -n $SANDBOX_HOME ]] && \
|
||
local directory="%{%F{3}%}$SANDBOX_NAME${PWD#$SANDBOX_HOME}%{%f%}" || \
|
||
local directory="%{%F{37}%}%~%{%f%}"
|
||
|
||
# Check we are in a git repository
|
||
local git=`~/.cache/zsh/git-prompt`
|
||
|
||
# If the last command failed, display its error code at the right
|
||
if [[ $exit_code -ne 0 ]]; then
|
||
local result=" %{%B%F{1}%}$exit_code%{%f%b%}"
|
||
fi
|
||
|
||
# If a virtualenv is enabled, display it's basename
|
||
if [[ ! -z "$VIRTUAL_ENV" ]]; then
|
||
local py=" %{%F{4}%}py%{%f%}%{%F{3}%}$(basename $VIRTUAL_ENV)%{%f%}"
|
||
fi
|
||
|
||
# If docker-machine env is active, display the machines name
|
||
if [[ ! -z "$DOCKER_MACHINE_NAME" ]]; then
|
||
local docker=" %{%F{6}%}$DOCKER_MACHINE_NAME%{%f%}"
|
||
fi
|
||
|
||
# Print the first line of the prompt
|
||
print -P "$time_stamp $directory$git$py$docker$result"
|
||
}
|
||
|
||
fresh_almostontop() {
|
||
clear
|
||
fresh_line_one
|
||
print -P "$PROMPT"'$1'
|
||
}
|
||
|
||
fresh_compile_git_prompt() {
|
||
# Compile a simple C program which parses the output of `git status
|
||
# --procelain` to greatly decrease the time taken to draw the prompt
|
||
local cache=~/.cache/zsh; [ ! -d $cache ] && mkdir -p $cache
|
||
if [ ! -f $cache/git-prompt ]; then
|
||
cc -x c -std=gnu99 -O3 -DNDEBUG -o $cache/git-prompt - 2> /dev/null << EOF
|
||
#include <ctype.h>
|
||
#include <stdarg.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
|
||
#define check(CONDITION) if (CONDITION) { exit(0); }
|
||
|
||
typedef struct process {
|
||
pid_t pid;
|
||
FILE* out;
|
||
} process_t;
|
||
|
||
process_t process_open(char* command) {
|
||
int fds[2];
|
||
check(pipe(fds));
|
||
int pid = fork();
|
||
check(pid == -1);
|
||
if (pid == 0) { // child process
|
||
close(fds[0]);
|
||
dup2(fds[1], STDOUT_FILENO);
|
||
dup2(STDOUT_FILENO, STDERR_FILENO);
|
||
char* argv[] = {"sh", "-c", command, NULL};
|
||
exit(execvp(argv[0], argv));
|
||
} else { // parent process
|
||
close(fds[1]);
|
||
process_t process = {pid, fdopen(fds[0], "rb")};
|
||
return process;
|
||
}
|
||
}
|
||
|
||
int process_close(process_t process) {
|
||
fclose(process.out);
|
||
int status;
|
||
check(process.pid != waitpid(process.pid, &status, 0));
|
||
if (WIFEXITED(status)) {
|
||
return WEXITSTATUS(status);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
const char* trim(char* str) {
|
||
char* end;
|
||
while (isspace((unsigned char)*str)) str++;
|
||
if (*str == 0) {
|
||
return str;
|
||
}
|
||
end = str + strlen(str) - 1;
|
||
while (end > str && isspace((unsigned char)*end)) {
|
||
end--;
|
||
}
|
||
end[1] = '\0';
|
||
return str;
|
||
}
|
||
|
||
const char* append(char* buffer, int count, ...) {
|
||
va_list list;
|
||
int i;
|
||
va_start(list, count);
|
||
for (i = 0; i < count; i++) {
|
||
strcat(buffer, va_arg(list, const char*));
|
||
}
|
||
va_end(list);
|
||
return buffer;
|
||
}
|
||
|
||
const char* inttostr(char* buffer, int value) {
|
||
sprintf(buffer, "%d", value);
|
||
return buffer;
|
||
}
|
||
|
||
int main() {
|
||
process_t process;
|
||
// check we are in a git repo
|
||
process = process_open("git rev-parse --git-dir");
|
||
check(process_close(process));
|
||
|
||
// get the current branch name
|
||
process = process_open("git symbolic-ref --short HEAD");
|
||
char branch_buf[256] = {};
|
||
fread(branch_buf, 1, sizeof(branch_buf), process.out);
|
||
if (process_close(process)) {
|
||
// current HEAD is not a symbolic ref
|
||
process = process_open("git rev-parse --abbrev-ref HEAD");
|
||
memset(branch_buf, 0, sizeof(branch_buf));
|
||
fread(branch_buf, 1, sizeof(branch_buf), process.out);
|
||
check(process_close(process));
|
||
if (strcmp("HEAD", trim(branch_buf)) == 0) {
|
||
// get the commit hash
|
||
process = process_open("git rev-parse --short HEAD");
|
||
memset(branch_buf, 0, sizeof(branch_buf));
|
||
fread(branch_buf, 1, sizeof(branch_buf), process.out);
|
||
check(process_close(process));
|
||
}
|
||
}
|
||
const char* branch = trim(branch_buf);
|
||
char prompt[1024] = {};
|
||
append(prompt, 3, " %{%F{66}%}", branch, "%{%f%}");
|
||
|
||
// get the upstream remote if one exists
|
||
char command[1024] = {};
|
||
append(command, 3, "git config branch.", branch, ".remote");
|
||
process = process_open(command);
|
||
char remote_buf[256] = {};
|
||
fread(remote_buf, 1, sizeof(remote_buf), process.out);
|
||
if (process_close(process) == 0) {
|
||
const char* remote = trim(remote_buf);
|
||
// get the number of commits ahead of the remote
|
||
memset(command, 0, sizeof(command));
|
||
process = process_open(append(command, 5,
|
||
"git rev-list --right-only refs/remotes/",
|
||
remote, "/", branch, "...HEAD --count"));
|
||
char count[32] = {};
|
||
fread(count, 1, sizeof(count), process.out);
|
||
if(process_close(process) == 0 && strcmp("0", trim(count))) {
|
||
append(prompt, 2, "↑", trim(count));
|
||
}
|
||
|
||
// get the number of commits behind the remote
|
||
memset(command, 0, sizeof(command));
|
||
process = process_open(append(command, 5,
|
||
"git rev-list --left-only refs/remotes/",
|
||
remote, "/", branch, "...HEAD --count"));
|
||
memset(count, 0, sizeof(count));
|
||
fread(count, 1, sizeof(count), process.out);
|
||
if(process_close(process) == 0 && strcmp("0", trim(count))) {
|
||
append(prompt, 2, "↓", trim(count));
|
||
}
|
||
}
|
||
append(prompt, 1, " ");
|
||
|
||
// get the status and parse it
|
||
process = process_open("git status --porcelain");
|
||
char status[2048];
|
||
int indexed = 0, modified = 0, deleted = 0, untracked = 0, unmerged = 0;
|
||
while (NULL != fgets(status, sizeof(status) - 1, process.out)) {
|
||
char X = status[0];
|
||
char Y = status[1];
|
||
if (X == '?' && Y == '?') {
|
||
++untracked;
|
||
} else if ((X == 'A' && (Y == 'A' || Y == 'U')) ||
|
||
(X == 'D' && (Y == 'D' || Y == 'U')) ||
|
||
(X == 'U' && (Y == 'A' || Y == 'D' || Y == 'D' || Y == 'U'))) {
|
||
++unmerged;
|
||
} else {
|
||
switch (X) {
|
||
case ' ':
|
||
switch (Y) {
|
||
case 'M': ++modified; break;
|
||
case 'D': ++deleted; break;
|
||
} break;
|
||
case 'D': ++indexed;
|
||
switch (Y) {
|
||
case ' ': break;
|
||
case 'M': ++modified; break;
|
||
} break;
|
||
case 'M': case 'A': case 'R': case 'C': ++indexed;
|
||
switch (Y) {
|
||
case ' ': break;
|
||
case 'M': ++modified; break;
|
||
case 'D': ++deleted; break;
|
||
} break;
|
||
}
|
||
}
|
||
}
|
||
check(process_close(process));
|
||
if (indexed || modified || deleted || unmerged || untracked) { // modified
|
||
char int_buf[32];
|
||
if (indexed) {
|
||
append(prompt, 3, "%{%F{2}%}*", inttostr(int_buf, indexed), "%{%f%}");
|
||
}
|
||
if (modified) {
|
||
append(prompt, 3, "%{%F{1}%}+", inttostr(int_buf, modified), "%{%f%}");
|
||
}
|
||
if (deleted) {
|
||
append(prompt, 3, "%{%F{1}%}-", inttostr(int_buf, deleted), "%{%f%}");
|
||
}
|
||
if (unmerged) {
|
||
append(prompt, 3, "%{%B%F{1}%}×", inttostr(int_buf, unmerged), "%{%f%}");
|
||
}
|
||
if (untracked) {
|
||
append(prompt, 1, "%{%F{1}%}…%{%f%}");
|
||
}
|
||
} else { // clean
|
||
append(prompt, 1, "%{%B%F{2}%}✓%{%f%b%}");
|
||
}
|
||
// print the prompt
|
||
puts(prompt);
|
||
return 0;
|
||
}
|
||
EOF
|
||
if [ $? -ne 0 ]; then
|
||
echo "git prompt not compiled, are the system development headers installed?"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
prompt_fresh_setup "$@"
|
||
|
||
# vim:ft=zsh
|