# -*- mode: sh -*-
#
# git-escape-magic - zle tweak for git command line arguments
#
# Copyright (c) 2011, 2012, 2014 Akinori MUSHA
# Licensed under the 2-clause BSD license.
#
# This tweak eliminates the need for manually escaping shell
# meta-characters such as [~^{}] that are used for specifying a git
# object (commit or tree).  Every time you type one of these
# characters on a git command line, it is automatically escaped with a
# backslash as necessary and as appropriate.
#
# If you want to use this with url-quote-magic, make sure to enable it
# first.
#
# Usage:
#     autoload -Uz git-escape-magic
#     git-escape-magic
#

git-escape-magic.self-insert() {
    emulate -L zsh
    setopt extendedglob
    local self_insert_function
    zstyle -s ':git-escape-magic' self-insert-function self_insert_function

    if [[ "$KEYS" == [{}~^]* ]] && {
        local qkey="${(q)KEYS}"
        [[ "$KEYS" != "$qkey" ]]
    } && {
        local lbuf="$LBUFFER$qkey"
        [[ "${(Q)LBUFFER}$KEYS" == "${(Q)lbuf}" ]]
    } && {
        local -a words
        words=("${(@Q)${(z)lbuf}}")
        [[ "$words[(i)(*/|)git(|-[^/]##)]" -le $#words ]]
    }
    then
        local i
        i="$words[(I)([;(){\&]|\&[\&\!]|\|\||[=<>]\(*)]"
        if [[ $i -gt 0 ]]; then
            shift $((i-1)) words
            if [[ "$words[1]" == [\=\<\>]\(* ]]; then
                words[1]="${words[1]#[=<>]\(}"
            else
                [[ "$words[1]" == \; && $words[2] == (then|else|elif|do) ]] && shift words
                shift words
            fi
        fi
        while [[ "$words[1]" == (if|while|until|\!) ]]; do
            shift words
        done
        while [[ "$words[1]" == [A-Za-z_][A-Za-z0-9_]#=* ]]; do
            shift words
        done
        [[ "$words[1]" == (*/|)git(|-[^/]##) ]] && {
            local subcommand
            subcommand="${words[1]##*/git-}"
            if [[ -z "$subcommand" ]]; then
                shift words
                subcommand="$words[1]"
            fi
            [[ $#words -ge 2 ]]
        } &&
        case "$subcommand" in
            # commands that may take pathspec but never take refspec with [{}~^]
            (add|rm|am|apply|check-attr|checkout-index|clean|clone|config|diff-files|hash-object|help|index-pack|mailinfo|mailsplit|merge-file|merge-index|mergetool|mktag|mv|pack-objects|pack-redundant|relink|send-email|show-index|show-ref|stage|status|verify-pack)
                false ;;
            # commands that may take pathspec but rarely take refspec with [{}~^]
            (for-each-ref|grep|ls-files|update-index)
                false ;;
            (archive|ls-tree)
                ! [[ $#words -ge 3 &&
                        "$words[-2]" == [^-]* ]] ;;
            (diff-tree)
                ! [[ $#words -ge 4 &&
                        "$words[-2]" == [^-]* &&
                        "$words[-3]" == [^-]* ]] ;;
            (*)
                [[ $words[(i)--] -gt $#words ]] ;;
        esac &&
        case "${words[-1]%%"$KEYS"}" in
            (*[@^])
                [[ "$KEYS" == [{~^]* ]] ;;
            (*[@^]\{[^}]##)
                [[ "$KEYS" == \}* ]] ;;
            (?*)
                [[ "$KEYS" == [~^]* ]] ;;
            (*)
                false ;;
        esac &&
        LBUFFER="$LBUFFER\\"
    fi

    zle "$self_insert_function"
}

git-escape-magic.on() {
    emulate -L zsh
    local self_insert_function="${$(zle -lL | awk \
        '$1=="zle"&&$2=="-N"&&$3=="self-insert"{print $4;exit}'):-.self-insert}"

    [[ "$self_insert_function" == git-escape-magic.self-insert ]] &&
        return 0

    # For url-quote-magic which does not zle -N itself
    zle -la "$self_insert_function" || zle -N "$self_insert_function"

    zstyle ':git-escape-magic' self-insert-function "$self_insert_function"

    zle -A git-escape-magic.self-insert self-insert
    return 0
}

git-escape-magic.off() {
    emulate -L zsh
    local self_insert_function
    zstyle -s ':git-escape-magic' self-insert-function self_insert_function

    [[ -n "$self_insert_function" ]] &&
        zle -A "$self_insert_function" self-insert
    return 0
}

zle -N git-escape-magic.self-insert
zle -N git-escape-magic.on
zle -N git-escape-magic.off

git-escape-magic() {
        git-escape-magic.on
}

[[ -o kshautoload ]] || git-escape-magic "$@"

