BenBE's humble thoughts Thoughts the world doesn't need yet …

22.08.2013

Mehr Farbe für Git

Filed under: Software — Schlagwörter: , , , — BenBE @ 23:38:16

So eine Kommandozeile ist in der Regel Grün auf Schwarz, zumindest, wenn man sein Terminal ganz klassisch eingerichtet hat.

Klassisch sieht dies meist so aus:

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi

Insbesondere wenn man mit einem Versionsverwaltungstool wie Git arbeitet, ist diese doch sehr kurze Ausgabe meist sehr unpraktisch, da mindestens der aktuelle Branch noch durchaus interessant gewesen wäre. Dieser ist ohne Weiteres hinzugefügt, aber so richtig spannend ist das alles noch nicht:

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[01;31m\]$(__git_ps1 " (%s)")\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w$(__git_ps1 " (%s)")\$ '
fi

Wo wir nun den aktuellen Branch schon einmal im Prompt enthalten haben, können wir das Ganze auch noch etwas ausbauen und uns eine Anzeige des Repository-Status ergänzen. Da diese Auswertung etwas aufwändiger ist, bauen wir den Code durch Auslagern in eine Funktion leicht um:

__git_ps2() {
    if __gitdir >/dev/null; then
        if ! git diff --no-ext-diff --quiet --cached --exit-code; then
            # staged changes
            echo -ne '\033[01;31m'
        elif ! git diff --no-ext-diff --quiet --exit-code; then
            # unstaged changes
            echo -ne '\033[01;33m'
        else
            # no changes
            echo -ne '\033[01;32m'
        fi
        __git_ps1 " (%s)"
    fi
}

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[01;31m\]$(__git_ps2)\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w$(__git_ps2)\$ '
fi

Okay, das sieht doch schon mal gut aus. Wenn wir aber eh schon auswerten, ob es Änderungen im Repository gibt, können wir auch gleich mit einblenden, wie viel sich geändert hat:

__git_ps2() {
    if __gitdir >/dev/null; then
        if ! git diff --no-ext-diff --quiet --cached --exit-code; then
            # staged changes
            echo -ne '\033[01;31m'
        elif ! git diff --no-ext-diff --quiet --exit-code; then
            # unstaged changes
            echo -ne '\033[01;33m'
        else
            # no changes
            echo -ne '\033[01;32m'
        fi
        STAT_S=`git diff --cached --stat|tail -1|awk '{print $1" "$4" "$6}'`
        STAT_U=`git diff --stat|tail -1|awk '{print $1" "$4" "$6}'`
        __git_ps1
        if [ "$STAT_U" != "" -o "$STAT_S" != "" ]; then
            echo -ne '\033[01;37m@['
            printf "T:\033[01;33m%d\033[01;37m{\033[01;32m+%d\033[01;37m/\033[01;31m-%d\033[01;37m}" $STAT_U
            if [ "$STAT_S" != "" ]; then
                echo -ne ' '
                printf "S:\033[01;33m%d\033[01;37m{\033[01;32m+%d\033[01;37m/\033[01;31m-%d\033[01;37m}" $STAT_S
            fi
            echo -ne ']'
        fi
    fi
}

Auch wenn wir theoretisch mit diesem Prompt bereits am Ziel sind, gibt es doch ein sehr subtiles Problem, welches sich zeigt, wenn man längere Befehle eingibt. Unter umständen fängt die Shell dann nämlich bereits vorzeitig an umzubrechen, obwohl die Befehlszeile noch bei Weitem nicht vollständig gefüllt ist. Insbesondere das Cursor-Handling ist unter diesen Umständen „kaputt“.

Wer jetzt annimmt, dass es hilft, in unserer Funktion einfach auch die Escape-Sequenzen für die Shell zu ergänzen und aus

            echo -ne '\033[01;37m@['

kurzerhand

            echo -ne '\[\033[01;37m\]@['

macht, wird feststellen, dass plötzlich \[ im Prompt angezeigt wird. Hier scheint die Shell alle Ausgaben, die durch eine Subshell im Prompt erzeugt werden, als literalen Text zu behandeln: Hierdurch werden alle Escape-Sequenzen in der Cursor-Position mitgezählt. Möchte man dies vermeiden, so ist ein wenig Arbeit nötig. Bevor wir uns aber darum kümmern, ergänzen wir noch kurz eine Info, ob wir gerade Root sind:

UIDCOLOR=32
[[ 0 == $UID ]] && UIDCOLOR=31
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;${UIDCOLOR}m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w$(__git_ps2)\033[00m\$ '

Aber zurück zu unserem Problem: Da die Shell die via Subshell ausgegebenen Zeichen in jedem Fall zählt und dabei auch Escape-Sequenzen einbezieht, müssten wir ein wenig das Problem umgehen: Mittels Escape-Sequenz merken wir uns die Position zuerst, geben dann eine Klartext-Version aus und drucken anschließend die farbige Version des Strings an der vorher gemerkten Position:

if [ "$color_prompt" = yes ]; then
    UIDCOLOR=32
    [[ 0 == $UID ]] && UIDCOLOR=31
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;'${UIDCOLOR}'m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[01;37m\]\[\0337\]$(__git_ps2 0)\[\0338$(__git_ps2 1)\]\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w$(__git_ps2 0)\$ '
fi

Ein kleines Detail ist in diesem Code-Abschnitt bereits angedeutet: Um zu Unterscheiden, ob wir die Klartext-Variante oder die farbige Version haben wollen. Hierzu müssen wir auch in unserer Funktion noch eine Kleinigkeit anpassen:

__git_ps2() {
    if __gitdir >/dev/null; then
        if ! git diff --no-ext-diff --quiet --cached --exit-code; then
            # staged changes
            [ $1 -ne 0 ] && echo -ne '\033[01;31m'
        elif ! git diff --no-ext-diff --quiet --exit-code; then
            # unstaged changes
            [ $1 -ne 0 ] && echo -ne '\033[01;33m'
        else
            # no changes
            [ $1 -ne 0 ] && echo -ne '\033[01;32m'
        fi
        STAT_S=`git diff --cached --stat|tail -1|awk '{print $1" "$4" "$6}'`
        STAT_U=`git diff --stat|tail -1|awk '{print $1" "$4" "$6}'`
        __git_ps1
        if [ $1 -ne 0 ]; then
            if [ "$STAT_U" != "" -o "$STAT_S" != "" ]; then
                echo -ne '\033[01;37m@['
                printf 'T:\033[01;33m%d\033[01;37m{\033[01;32m+%d\033[01;37m/\033[01;31m-%d\033[01;37m}' $STAT_U
                if [ "$STAT_S" != "" ]; then
                    echo -ne ' '
                    printf 'S:\033[01;33m%d\033[01;37m{\033[01;32m+%d\033[01;37m/\033[01;31m-%d\033[01;37m}' $STAT_S
                fi
                echo -ne ']'
            fi
        else
            if [ "$STAT_U" != "" -o "$STAT_S" != "" ]; then
                echo -ne '@['
                printf 'T:%d{+%d/-%d}' $STAT_U
                if [ "$STAT_S" != "" ]; then
                    echo -ne ' '
                    printf 'S:%d{+%d/-%d}' $STAT_S
                fi
                echo -ne ']'
            fi
        fi
    fi
}

Und fertig sind wir. Wem dies alles zu bunt ist, kann mit dem nachfolgenden Prompt wieder ganz klassisch arbeiten:

PS1='C:$(pwd | sed s/\\\\//\\\\\\\\/g)> '

Flattr this!

Keine Kommentare »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress