function abbr --description "Manage abbreviations"
    # parse arguments
    set -l mode
    set -l mode_flag # the flag that was specified, for better errors
    set -l mode_arg
    set -l needs_arg no
    while set -q argv[1]
        set -l new_mode
        switch $argv[1]
            case '-h' '--help'
                __fish_print_help abbr
                return 0
            case '-a' '--add'
                set new_mode add
                set needs_arg multi
            case '-r' '--rename'
                set new_mode rename
                set needs_arg double
            case '-e' '--erase'
                set new_mode erase
                set needs_arg single
            case '-l' '--list'
                set new_mode list
            case '-s' '--show'
                set new_mode show
            case '--'
                set -e argv[1]
                break
            case '-*'
                printf ( _ "%s: invalid option -- %s\n" ) abbr $argv[1] >&2
                return 1
            case '*'
                break
        end
        if test -n "$mode" -a -n "$new_mode"
            # we're trying to set two different modes
            printf ( _ "%s: %s cannot be specified along with %s\n" ) abbr $argv[1] $mode_flag >&2
            return 1
        end
        set mode $new_mode
        set mode_flag $argv[1]
        set -e argv[1]
    end

    # If run with no options, treat it like --add if we have an argument, or
    # --show if we do not have an argument
    if not set -q mode[1]
        if set -q argv[1]
            set mode add
            set needs_arg multi
        else
            set mode show
        end
    end

    if test $needs_arg = single
        set mode_arg $argv[1]
        set needs_arg no
        set -e argv[1]
    else if test $needs_arg = double
        # Pull the two parameters from argv.
        # * leave argv non-empty, if there are more than two arguments
        # * leave needs_arg set to double if there is not enough arguments
        if set -q argv[1]
            set param1 $argv[1]
            set -e argv[1]
            if set -q argv[1]
                set param2 $argv[1]
                set needs_arg no
                set -e argv[1]
            end
        end
    else if test $needs_arg = multi
        set mode_arg $argv
        set needs_arg no
        set -e argv
    end
    if test $needs_arg != no
        printf ( _ "%s: option requires an argument -- %s\n" ) abbr $mode_flag >&2
        return 1
    end

    # none of our modes want any excess arguments
    if set -q argv[1]
        printf ( _ "%s: Unexpected argument -- %s\n" ) abbr $argv[1] >&2
        return 1
    end

    switch $mode
        case 'add'
            # Convert from old "key=value" syntax
            # TODO: This should be removed later
            if not set -q mode_arg[2]
                and string match -qr '^[^ ]+=' -- $mode_arg
                set mode_arg (string split "=" -- $mode_arg)
            end

            # Bail out early if the exact abbr is already in
            set -q fish_user_abbreviations
            and contains -- "$mode_arg" $fish_user_abbreviations
            and return 0

            set -l key $mode_arg[1]
            set -e mode_arg[1]
            set -l value "$mode_arg"
            # Because we later store "$key $value", there can't be any spaces in the key
            if string match -q "* *" -- $key
                printf ( _ "%s: abbreviation cannot have spaces in the key\n" ) abbr >&2
                return 1
            end
            if test -z "$value"
                printf ( _ "%s: abbreviation must have a value\n" ) abbr >&2
                return 1
            end
            if set -l idx (__fish_abbr_get_by_key $key)
                # erase the existing abbreviation
                set -e fish_user_abbreviations[$idx]
            end
            if not set -q fish_user_abbreviations
                # initialize as a universal variable, so we can skip the -U later
                # and therefore work properly if someone sets this as a global variable
                set -U fish_user_abbreviations
            end
            set fish_user_abbreviations $fish_user_abbreviations "$key $value"
            return 0

        case 'rename'
            set -l old_name $param1
            set -l new_name $param2

            # if the target name already exists, throw an error
            if set -l idx (__fish_abbr_get_by_key $new_name)
                printf ( _ "%s: abbreviation '%s' already exists, cannot rename\n" ) abbr $new_name >&2
                return 2
            end

            # Because we later store "$key $value", there can't be any spaces in the key
            if string match -q "* *" -- $new_name
                printf ( _ "%s: abbreviation cannot have spaces in the key\n" ) abbr >&2
                return 1
            end

            set -l idx (__fish_abbr_get_by_key $old_name)
            or begin
                printf ( _ "%s: no such abbreviation '%s'\n" ) abbr $old_name >&2
                return 2
            end

            set -l value (string split " " -m 1 -- $fish_user_abbreviations[$idx])[2]
            set fish_user_abbreviations[$idx] "$new_name $value"
            return 0

        case 'erase'
            if set -l idx (__fish_abbr_get_by_key $mode_arg)
                set -e fish_user_abbreviations[$idx]
                return 0
            else
                printf ( _ "%s: no such abbreviation '%s'\n" ) abbr $mode_arg >&2
                return 2
            end

        case 'show'
            for i in $fish_user_abbreviations
                set -l opt_double_dash
                set -l kv (string split " " -m 1 -- $i)
                set -l key $kv[1]
                set -l value $kv[2]

                # Check to see if either key or value has a leading dash
                # If so, we need to write --
                string match -q -- '-*' $key $value
                and set opt_double_dash '--'
                echo abbr $opt_double_dash (string escape -- $key $value)
            end
            return 0

        case 'list'
            for i in $fish_user_abbreviations
                set -l key (string split " " -m 1 -- $i)[1]
                printf "%s\n" $key
            end
            return 0
    end
end

function __fish_abbr_get_by_key
    if not set -q argv[1]
        echo "__fish_abbr_get_by_key: expected one argument, got none" >&2
        return 2
    end

    set -q fish_user_abbreviations
    or return 1

    # Going through all entries is still quicker than calling `seq`
    set -l keys
    for kv in $fish_user_abbreviations
        # If this does not match, we have screwed up before and the error should be reported
        set keys $keys (string split " " -m 1 -- $kv)[1]
    end
    if set -l idx (contains -i -- $argv[1] $keys)
        echo $idx
        return 0
    end
    return 1
end
