oniony / TMSU

TMSU lets you tags your files and then access them through a nifty virtual filesystem from any other application.
Other
2.04k stars 119 forks source link

Tab completion for bash #78

Open ghost opened 8 years ago

ghost commented 8 years ago

It would be cool to have tab completion for bash too.

oniony commented 8 years ago

I'll have to have a look at this. Completion was quite tricky under Zsh but Zsh completion is pretty sophisticated compared to Bash. I'll see how easy it is to add this. On 23 Jun 2016 16:53, "fturco" notifications@github.com wrote:

It would be cool to have tab completion for bash too.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/oniony/TMSU/issues/78, or mute the thread https://github.com/notifications/unsubscribe/AAAXB0HLW2II3dP5JwnHl5wTGpCk3n9Iks5qOqwOgaJpZM4I89lm .

tomassedovic commented 8 years ago

I've written this a few months ago. It's a very rudimentary bash completion -- all it supports is completing tag names and values for tmsu tag. It doesn't handle values with spaces and none of the other subcommands.

Might be a usable stopgap for people.

# This belongs to /etc/bash_completion.d/tmsu

# TODO: handle spaces in values.

have tmsu &&
_tmsu()
{
    local cur prev

    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}
    prev_prev=${COMP_WORDS[COMP_CWORD-2]}

    if [ "$COMP_CWORD" -eq 1 ]; then
        COMPREPLY=( $(compgen -W "config copy delete dupes files help imply init merge mount rename repair stats status tag tags unmount untag untagged values version vfs" -- ${cur}) );
        return 0
    elif [ "$COMP_CWORD" = "2" ]; then
        _filedir
        return 0
    else
        case "${COMP_WORDS[1]}" in
        tag)
            if [ "$cur" = "=" ]; then
                COMPREPLY=( $(compgen -W "$(tmsu values ${prev} 2> /dev/null)" -- "") )
            elif [ "$prev" = "=" ]; then
                COMPREPLY=( $(compgen -W "$(tmsu values ${prev_prev})" -- $cur) )
            else
                COMPREPLY=( $(compgen -W "$(tags)" -- $cur) )
            fi
            return 0
            ;;
        esac
    fi

}

function tags()
{
    # Select all tag/value pairs for the completion.
    # NOTE: there is no tag/value association without it being applied to a file.
    # So some values don't show up because there's no file for them yet (or anymore).
    local TAG_VALUE_QUERY='SELECT DISTINCT tag.name || "=" || value.name FROM file_tag JOIN tag ON tag_id=tag.id JOIN value ON value_id=value.id;';
    echo $(sqlite3 "$HOME/.tmsu/default.db" "$TAG_VALUE_QUERY")

    # Select the remaining tags (i.e. all tags without any value associated with them)
    # (we don't want to offer a "value tag" again in its empty form)
    local STANDALONE_TAGS='SELECT DISTINCT name FROM tag LEFT OUTER JOIN file_tag ON tag_id=id WHERE (value_id IS NULL) OR (value_id IS 0);'
    echo $(sqlite3 "$HOME/.tmsu/default.db" "$STANDALONE_TAGS")
};

complete -F _tmsu tmsu
Master-jim commented 5 years ago

Hi,

I completed the bash_completion script to handle help command, complete with tags and filenames. This way you can type: tmsu tag myfile.pdf <TAB> to have the first tag, then <TAB> again to complete a second one, etc. There are still work to do to support the completion of options like --tags= tmsu tag --tags="landscape" field1.jpg field2.jpg

# This belongs to /etc/bash_completion.d/tmsu or ~/.bash_completion
# Original from tomassedovic (https://github.com/oniony/TMSU/issues/78)
# TODO: handle spaces in values.
have tmsu &&
_tmsu()
{
    local cur prev

    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}
    prev_prev=${COMP_WORDS[COMP_CWORD-2]}

    if [ "$COMP_CWORD" -eq 1 ]; then
        COMPREPLY=( $(compgen -W "config copy delete dupes files help imply init merge mount rename repair stats status tag tags unmount untag untagged values version vfs" -- ${cur}) );
        return 0
    else
        case "${COMP_WORDS[1]}" in
        tag|files|untag)
            local IFS=$'\n'
            if [ "$cur" = "=" ]; then
                # Complete values for a tag (ex: year=), so no need for filenames
                COMPREPLY=( $(compgen -W "$(tmsu values ${prev} 2> /dev/null)" -- "") )
            elif [ "$prev" = "=" ]; then
                # If previous "word" is symbol "=", used only when the "=" is not escaped
                COMPREPLY=( $(compgen -f -W "$(tmsu values ${prev_prev})" -- $cur) )
            else
                # NORMAL case: let's print the tags, then filenames with the separator between.
                # The if is used to show only the separator if there are filenames
                COMPREPLY=( $(compgen  -W "$(tags)" -- $cur) $( if [ "" != "$(compgen -f -- $cur)" ]; then echo "##########__Fichiers__##########"; fi) $(compgen -f -- $cur))
            fi
            return 0
            ;;
    help)
            COMPREPLY=( $(compgen -W "config copy delete dupes files help imply init merge mount rename repair stats status tag tags unmount untag untagged values version vfs" -- ${cur}) );
            return 0
        ;;
    *)
        _filedir
            return 0
            ;;
        esac
    fi

}

function tags()
{
    DB=$(tmsu info 2>&1 | sed -n -e "/Database/{s/Database: //;p}")
    if [ -e "${DB}" ]; then 
        # Select all tag/value pairs for the completion.
        # NOTE: there is no tag/value association without it being applied to a file.
        # So some values don't show up because there's no file for them yet (or anymore).
        local TAG_VALUE_QUERY='SELECT DISTINCT tag.name || "=" || value.name FROM file_tag JOIN tag ON tag_id=tag.id JOIN value ON value_id=value.id order by tag.name;';
        sqlite3 "${DB}" "$TAG_VALUE_QUERY"

        # Select the remaining tags (i.e. all tags without any value associated with them)
        # (we don't want to offer a "value tag" again in its empty form)
        local STANDALONE_TAGS='SELECT DISTINCT name FROM tag LEFT OUTER JOIN file_tag ON tag_id=id WHERE (value_id IS NULL) OR (value_id IS 0) order by name;'
        sqlite3 "${DB}" "$STANDALONE_TAGS"
fi;
};
# Use of option "nosort" because it is handled in the SQL requests and permits to show the filenames at the end
complete  -o nosort -F _tmsu tmsu
kvngvikram commented 3 years ago

Well, I wrote a custom modification for completion with fzf. People who came here searching for related things, have a look https://github.com/kvngvikram/custom_tmsu#custom_tmsu

shanness commented 3 years ago

Nice @kvngvikram, @tomassedovic and @Master-jim , these two scripts work very well together :) Basically I moved /usr/bin/tmsu to /usr/bin/tmsu_original and put the custom one in /usr/bin/tmsu Now I get tab completion, AND the nice fzf popup tag finder.

I made a small adjustment to not show the file list if using the "files" command (which doesn't take file names). (and stored the tmsu main command for reuse). And also added an option to summarize or display the values like so (which required adding = to the COMP_WORDBREAKS variable, which fixed what I think was a bug in the matching it in your script), because I have hundreds of values in some of my value tags). Configured like so

# Use this to control if values are displayed or just summarized, they will be expanded after the = is typed/selected
#VALUES="display"
VALUES="summarize"
COMP_WORDBREAKS="${COMP_WORDBREAKS}="
$ tmsu files <tab>
for= 5 values     
star= 151 values  
studio= 17 values 
BEST       
...
$ tmsu files for
for= 5 values     foreign_language  
$ tmsu files for=
cat      claudia  joe   meow    sharni 

And also implemented tab completion on the values command

$ tmsu values <tab>
for      star     studio   
$ tmsu values for<enter>
cat  claudia  joe  meow  sharni
# This belongs to /etc/bash_completion.d/tmsu or ~/.bash_completion
# Original from tomassedovic (https://github.com/oniony/TMSU/issues/78)
# TODO: handle spaces in values.

# Use this to control if values are displayed or just summarized, they will be expanded after the = is typed/selected
#VALUES="display"
VALUES="summarize"
COMP_WORDBREAKS="${COMP_WORDBREAKS}="

have tmsu &&
_tmsu()
{
    local cur prev prev_prev tmsu_command

    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}
    prev_prev=${COMP_WORDS[COMP_CWORD-2]}
    tmsu_command="${COMP_WORDS[1]}"

    if [ "$COMP_CWORD" -eq 1 ]; then
        COMPREPLY=( $(compgen -W "config copy delete dupes files help imply init merge mount rename repair stats status tag tags unmount untag untagged values version vfs" -- ${cur}) );
        return 0
    else
        case "${tmsu_command}" in
        tag|files|untag)
            local IFS=$'\n'
            if [ "$cur" = "=" ]; then
                # Complete values for a tag (ex: year=), so no need for filenames
                COMPREPLY=( $(compgen -W "$(tmsu values ${prev} 2> /dev/null)" -- "") )
            elif [ "$prev" = "=" ]; then
                # If previous "word" is symbol "=", used only when the "=" is not escaped
                COMPREPLY=( $(compgen -f -W "$(tmsu values ${prev_prev})" -- $cur) )
            else
                # NORMAL case: let's print the tags, then filenames with the separator between.
                # The if is used to show only the separator if there are filenames
                if [[ ${tmsu_command} == files ]]; then # Don't show filenames for the files command (which only takes tags)
                    COMPREPLY=( $(compgen  -W "$(tags)" -- $cur))
                else
                    COMPREPLY=( $(compgen  -W "$(tags)" -- $cur) $( if [ "" != "$(compgen -f -- $cur)" ]; then echo "##########__FILES__##########"; fi) $(compgen -f -- $cur))
                fi
            fi
            return 0
            ;;
        values)
            COMPREPLY=( $(compgen  -W "$(tags | grep '=' | cut -f1 -d'=')" -- $cur))
            return 0
            ;;
    help)
            COMPREPLY=( $(compgen -W "config copy delete dupes files help imply init merge mount rename repair stats status tag tags unmount untag untagged values version vfs" -- ${cur}) );
            return 0
        ;;
    *)
        _filedir
            return 0
            ;;
        esac
    fi

}

function tags()
{
    DB=$(tmsu info 2>&1 | sed -n -e "/Database/{s/Database: //;p}")
    if [ -e "${DB}" ]; then 
        # Select all tag/value pairs for the completion.
        # NOTE: there is no tag/value association without it being applied to a file.
        # So some values don't show up because there's no file for them yet (or anymore).
        if [[ $VALUES == summarize ]]; then
            local TAG_VALUE_QUERY='SELECT tag.name || "= " || count(distinct value.name) || " values" FROM file_tag JOIN tag ON tag_id=tag.id JOIN value ON value_id=value.id group by tag.name order by tag.name;';
        else
            local TAG_VALUE_QUERY='SELECT DISTINCT tag.name || "=" || value.name FROM file_tag JOIN tag ON tag_id=tag.id JOIN value ON value_id=value.id order by tag.name;';
        fi
        sqlite3 "${DB}" "$TAG_VALUE_QUERY"

        # Select the remaining tags (i.e. all tags without any value associated with them)
        # (we don't want to offer a "value tag" again in its empty form)
        local STANDALONE_TAGS='SELECT DISTINCT name FROM tag LEFT OUTER JOIN file_tag ON tag_id=id WHERE (value_id IS NULL) OR (value_id IS 0) order by name;'
        sqlite3 "${DB}" "$STANDALONE_TAGS"
fi;
};
# Use of option "nosort" because it is handled in the SQL requests and permits to show the filenames at the end
complete  -o nosort -F _tmsu tmsu
shanness commented 3 years ago

I've saved this to a gist if anyone wants to improve or fork it. https://gist.github.com/shanness/a4855a397ed6568b946f46fbc6fdbb09