oil icon indicating copy to clipboard operation
oil copied to clipboard

Feature request: auto complete tags

Open QiangF opened this issue 7 years ago • 7 comments

I use firefox's bookmark manager, it has the convenience of auto complete tags and make the tagging system more consistant, i.e. few typos. But manage bookmarks in firefox is too slow when the number of bookmarks grow. Auto complete tags will save typing in case of a very long tag as well.

QiangF avatar May 12 '17 01:05 QiangF

Maybe tagging can be done in a tagging loop. keep asking for a tag, get the user's input, print matches, use tab to autocomplete common part, quit adding tags with esc.

QiangF avatar May 12 '17 01:05 QiangF

I have a skeleton here:

function listTags {
    buku -t
}

function formatTags {
}

function searchAndSelectTags {
    listTags |
    formatTags |
    searchAndAddAsYouType
}

The output of buku -t is:

 1. bookmark (1)
 2. hello (1)

The nuber in the beginning and at the end has to be removed. searchAndAddAsYouType will continuously adding tags.

QiangF avatar May 13 '17 02:05 QiangF

Hi there! Thanks for the suggestion. Unfortunately, I am terribly short on time at the moment since I'm dealing with a bit of a medical situation.

My original plan was to avoid adding features to this project because it is written in bash and because I did not find a satisfactory method for unit testing. But rather to port the project to haskell first and continue from a code base that is less likely to deteriorate while growing.

But since this will be considerably delayed as well, I think the feature should be added here.

And I will do so, when I find the time. That could be soon, or it could take months. I am sorry I can't be more precise at the moment :/

Until then

AndreiUlmeyda avatar May 13 '17 12:05 AndreiUlmeyda

I am still working on the script, I think fzf is much better than peco, I am trying to hack askUserForTags with the following:

function searchAsYouType {
    # to accept the current typed string, press ctrl-m
    # to copy selected lines to clipboard, press ctrl-w
    if [[ "$forceMultilineSelection" == true ]]; then
        fzf --multi --cycle --bind 'tab:toggle-up,btab:toggle-down,ctrl-m:print-query,ctrl-w:execute-multi(echo {} | xclip -selection clipboard)+abort'
    else
        fzf --cycle --bind 'tab:toggle-up,btab:toggle-down,ctrl-m:print-query,ctrl-y:execute(echo {} | xclip -selection clipboard)+abort'
    fi
}

function searchTagAsYouType {
    # display a header on top of all candidates
    fzf --multi --cycle --header "$1" --bind 'tab:toggle-up,btab:toggle-down,ctrl-m:print-query,ctrl-w:execute-multi(echo {} | xclip -selection clipboard)+abort'
}


function listTags {
    buku -t
}

function extractTags {
    # The output of buku -t is:
    # 1. bookmark (1)
    # 2. hello (1)
}

function joinSelections {
    # concatenate tag with ,
    selection="$1"
    output=''
    for element in ${selection[@]}; do
        output=$output,$element
    done
    echo $output
}

function myAddTags {
    # ask for tag in a loop
    tags=''
    while true
        # display already selected tags as fzf header, which is shown on the top of the list
        newTag=$(searchTagAsYouType $tags $@)
        newTag=joinSelections $newTag
        # join tags with ','
        tags=$tags,$newTag
        # fzf returns 130 if Interrupted with CTRL-C or ESC
        # use that to break out of the loop
        if [ $? = 130 ] ; then
            break ;
        fi
    done
}

function askUserForTags {
    listTags |
    extractTags |
    myAddTags |
    echo "$tags"
}

QiangF avatar Dec 28 '17 09:12 QiangF

@QiangF

Did you ever get some form of this working? Oil is a great tool, but auto-completion of tags name would make it that much better and solve a couple workflow issues I have run into.

Also, are you using 'fzf' in place of 'peco' entirely?

cheers.

vredesbyyrd avatar Aug 27 '18 19:08 vredesbyyrd

I am still working on it:

#!/bin/bash

# globals
LIBDIR=$HOME/.dotfiles/bin/oil
mode="open"
forceMultilineSelection=true
declare -a selection

# parse command line flags
function parseFlags {
    parsingLibraryError="error: 'getopt' missing"
    helpText="-t/--tag, -T/--title, -d/--delete"

    # check if getopt is available
    getopt --test > /dev/null
    if [[ $? -ne 4 ]]; then
        echo "$parsingLibraryError"
        exit 1
    fi

    # specify valid flags
    shortOptions=hatTdn
    longOptions=help,add,tag,title,delete,no-multilineSelection

    parsed=$(getopt --options $shortOptions --longoptions $longOptions --name "$0" -- "$@")

    # exit if parsing arguments failed
    if [[ $? -ne 0 ]]; then
        exit 1
    fi

    while true; do
        case "$1" in
            -h|--help) echo "$helpText"; exit 0;;
            -a|--add) mode="add"; skipSelection=true; shift;;
            -t|--tag) mode="tag"; shift;;
            -T|--title) mode="title"; shift;;
            -d|--delete) mode="delete"; shift;;
            -n|--no-multilineSelection) forceMultilineSelection=false; shift;;
            *) break;;
        esac
    done
}

function updateTitle {
    index="${1#*|}"
    buku --print "$index"
    read -r -p "Enter a new title for the bookmark above: " newTitle
    if [[ -n $newTitle ]]; then
        buku --tacit --update "$index" --title "$newTitle"
    else
        echo "The new title was empty, no update was done."
    fi
}

function updateTitles {
    forEachBookmark updateTitle
}

function deleteBookmark {
    url="${urlAndIndex%|*}"
    withoutTrailingSlash="${url%/}"
    buku --sany "$withoutTrailingSlash" --delete
}

function deleteBookmarks {
    forEachBookmark deleteBookmark
}

function tagBookmark {
    index="${1#*|}"
    buku --update "$index" --tag "$tags"
}

function addTag {
    index="${1#*|}"
    buku --update "$index" --tag + "$tags"
}

function removeTag {
    index="${1#*|}"
    buku --update "$index" --tag + "$tags"
}

function tagBookmarks {
    tags=$(askUserForTags)
    forEachBookmark tagBookmark
}

function openInBrowser {
    url=${urlAndIndex%|*}
    xdg-open "$url"
}

function forEachBookmark {
    operation=$1
    for urlAndIndex in ${selection[@]}; do
        ${operation} "$urlAndIndex"
    done
}

function openBookmarks {
    forEachBookmark openInBrowser
}

function exitOnEmptySelection {
    if [[ -z "${selection[@]}" ]]; then
        exit 0
    fi
}

function formatColumns {
    awk -f "$LIBDIR"/format-columns.filter
}

function jsonToLine {
    jq -fr "$LIBDIR"/json-to-line.filter
}

function bookmarksAsJson {
    buku --print --json
}

function searchAndSelectBookmarks {
    bookmarksAsJson |
    jsonToLine |
    formatColumns |
    searchAsYouType
}

function askForTitleAndTagsThenAdd {
    url=$1
    read -p "Enter a title to set a custom one, leave empty otherwise: " customTitle
    tags=$(askUserForTags)
    addBookmark="buku --add $url"
    if [[ -n "$customTitle" ]]; then
        addBookmark="$addBookmark --title $customTitle"
    fi
    if [[ -n "$tags" ]]; then
        addBookmark="$addBookmark --tag $tags"
    fi
    $addBookmark
}

function addBookmarkFromClipboard {
    urlFromClipboard=$(xsel)

    if [[ -n "$urlFromClipboard" ]]; then
        echo "URL is:"
        echo "$urlFromClipboard"
        # If the URL contains characters like ;, & or brackets they may be interpreted
        # specially by the shell. To avoid it, add the URL within single or double quotes.
        askForTitleAndTagsThenAdd "'$urlFromClipboard'"
    else
        echo "Clipboard is empty, not adding any bookmarks."
        exit 0
    fi

    exit 0
}

function processBookmarks {
    if [[ "$mode" == "add" ]]; then
        addBookmarkFromClipboard
        exit 0
    fi

    selection=$(searchAndSelectBookmarks)
    echo "selection is:"
    echo "$selection"
    exitOnEmptySelection

    case "$mode" in
        open) openBookmarks;;
        tag) tagBookmarks;;
        title) updateTitles;;
        delete) deleteBookmarks;;
    esac
}

# q
# modify askUserForTags to allow multiple tags with auto completion
# askUserForTagsWithCompletion

function searchAsYouType {
    # to accept the current typed string, press ctrl-m
    # to copy selected lines to clipboard, press ctrl-w
    if [[ "$forceMultilineSelection" == true ]]; then
        fzf --multi --cycle --bind 'tab:toggle-up,btab:toggle-down,ctrl-w:execute-multi(echo {} | xclip -selection clipboard)+abort'
    else
        fzf --cycle --bind 'tab:toggle-up,btab:toggle-down,ctrl-y:execute(echo {} | xclip -selection clipboard)+abort'
    fi
}

function askUserForTags {
    read -p "Input the comma separated list of tags to add: " tags
    echo "$tags"
}

# run
parseFlags "$@"
processBookmarks

QiangF avatar Aug 28 '18 02:08 QiangF

Awesome, thanks for sharing. I am going to take a look at this when I have sometime tomorrow. Cheers.

vredesbyyrd avatar Aug 28 '18 02:08 vredesbyyrd