aboutsummaryrefslogtreecommitdiff
path: root/pdksh/pdkshrc.d/prompt.pdksh
blob: c446d3feb542b0621312ef8b562292ebd49caed9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# All of this is only known to work on OpenBSD's fork of pdksh
[[ $(uname -s) == OpenBSD ]] || return

# Frontend to controlling prompt
prompt() {

    # If no arguments, print the prompt strings as they are
    if ! (($#)) ; then
        printf '%s\n' PS1="$PS1" PS2="$PS2" PS3="$PS3" PS4="$PS4"
        return
    fi

    # What's done next depends on the first argument to the function
    case $1 in

        # Turn complex, colored PS1 and debugging PS4 prompts on
        on)
            # Basic prompt shape depends on whether we're in SSH or not
            PS1=
            if [[ -n $SSH_CLIENT ]] || [[ -n $SSH_CONNECTION ]] ; then
                PS1=$PS1'\u@\h:'
            fi
            PS1=$PS1'\w'

            # Add sub-commands; VCS, job, and return status checks
            PS1=$PS1'$(prompt vcs)$(prompt job)'

            # Add prefix and suffix
            PS1='${PROMPT_PREFIX}'$PS1'${PROMPT_SUFFIX}'

            # Add terminating "$" or "#" sign
            PS1=$PS1'\$'

            # Count available colors
            typeset -i colors
            colors=$( {
                tput Co || tput colors
            } 2>/dev/null )

            # Prepare reset code
            typeset reset
            reset=$( {
                tput me || tput sgr0
            } 2>/dev/null )

            # Decide prompt color formatting based on color availability
            typeset format
            case $colors in

                # Check if we have non-bold bright green available
                256)
                    format=$( {
                        : "${PROMPT_COLOR:=12}"
                        tput AF "$PROMPT_COLOR" ||
                        tput setaf "$PROMPT_COLOR" ||
                        tput AF "$PROMPT_COLOR" 0 0  ||
                        tput setaf "$PROMPT_COLOR" 0 0
                    } 2>/dev/null )
                    ;;

                # If we have only eight colors, use bold green
                8)
                    format=$( {
                        : "${PROMPT_COLOR:=4}"
                        tput AF "$PROMPT_COLOR" ||
                        tput setaf "$PROMPT_COLOR"
                        tput md || tput bold
                    } 2>/dev/null )
                    ;;

                # For all other terminals, we assume non-color (!), and we just
                # use bold
                *)
                    format=$( {
                        tput md || tput bold
                    } 2>/dev/null )
                    ;;
            esac

            # String it all together
            PS1='\['"$format"'\]'"$PS1"'\['"$reset"'\] '
            PS2='> '
            PS3='? '
            PS4='+<$?> $LINENO:'
            ;;

        # Git prompt function
        git)
            # Bail if we're not in a work tree--or, implicitly, if we don't
            # have git(1).
            typeset iswt
            iswt=$(git rev-parse --is-inside-work-tree 2>/dev/null)
            [[ $iswt = true ]] || return

            # Refresh index so e.g. git-diff-files(1) is accurate
            git update-index --refresh >/dev/null 2>&1

            # Find a local branch, remote branch, or tag (annotated or not), or
            # failing all of that just show the short commit ID, in that order
            # of preference; if none of that works, bail out
            typeset name
            name=$( {
                git symbolic-ref --quiet HEAD ||
                git describe --tags --exact-match HEAD ||
                git rev-parse --short HEAD
            } 2>/dev/null) || return
            name=${name##*/}
            [[ -n $name ]] || return

            # Check various files in .git to flag processes
            typeset proc
            [[ -d .git/rebase-merge || -d .git/rebase-apply ]] &&
                proc=${proc:+$proc,}'REBASE'
            [[ -f .git/MERGE_HEAD ]] &&
                proc=${proc:+$proc,}'MERGE'
            [[ -f .git/CHERRY_PICK_HEAD ]] &&
                proc=${proc:+$proc,}'PICK'
            [[ -f .git/REVERT_HEAD ]] &&
                proc=${proc:+$proc,}'REVERT'
            [[ -f .git/BISECT_LOG ]] &&
                proc=${proc:+$proc,}'BISECT'

            # Collect symbols representing repository state
            typeset state

            # Upstream HEAD has commits after local HEAD; we're "behind"
            typeset -i behind
            behind=$(git rev-list --count 'HEAD..@{u}' 2>/dev/null)
            ((behind)) && state=${state}'<'

            # Local HEAD has commits after upstream HEAD; we're "ahead"
            typeset -i ahead
            ahead=$(git rev-list --count '@{u}..HEAD' 2>/dev/null)
            ((ahead)) && state=${state}'>'

            # Tracked files are modified; double exclamation mark because
            # that's how you get a literal one in pdksh PS1
            git diff-files --no-ext-diff --quiet ||
                state=${state}'!!'

            # Changes are staged
            git diff-index --cached --no-ext-diff --quiet HEAD 2>/dev/null ||
                state=${state}'+'

            # There are some untracked and unignored files
            git ls-files --directory --error-unmatch --exclude-standard \
                --no-empty-directory --others -- ':/*' >/dev/null 2>&1 &&
                state=${state}'?'

            # There are stashed changes
            git rev-parse --quiet --verify refs/stash >/dev/null &&
                state=${state}'^'

            # Print the status in brackets; add a git: prefix only if there
            # might be another VCS prompt (because PROMPT_VCS is set)
            printf '(%s%s%s%s)' \
                "${PROMPT_VCS:+git:}" "$name" "${proc:+:$proc}" "$state"
            ;;

        # Revert to simple inexpensive prompts
        off)
            PS1='\$ '
            PS2='> '
            PS3='? '
            PS4='+ '
            ;;

        # VCS wrapper prompt function; print the first relevant prompt, if any
        vcs)
            typeset vcs
            for vcs in "${PROMPT_VCS[@]:-git}" ; do
                prompt "$vcs" && return
            done
            ;;

        # Show the count of background jobs in curly brackets, if not zero
        job)
            typeset -i jobc
            jobc=$(jobs -p | sed -n '$=')
            ((jobc)) && printf '{%u}' "$jobc"
            ;;

        # Print error
        *)
            printf 'prompt: Unknown command %s\n' "$1" >&2
            return 2
            ;;

    esac
}

# Start with full-fledged prompt
prompt on