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 #include #include #include #include #include #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