From 5e238cb53868ff6f3f9214fcce7e25fbe96548d2 Mon Sep 17 00:00:00 2001 From: Brett Terpstra Date: Thu, 16 Sep 2021 00:41:09 -0500 Subject: [PATCH] fzf integration --- CHANGELOG.md | 35 ++++++++++++ Readme.md | 21 ++++++- qq | 152 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 151 insertions(+), 57 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a0f03ba --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# CHANGELOG + +## 1.2.4 + +- Way better searching, progressively fuzzier until a match is found +- Only ever show the best result, no point in showing a ton of matches +- Use fzf if available, for searching and for filtering multiple matches + +## 1.2.3 + +- Bugfixes + +## 1.2.2 + +- OR query when AND failed was way too broad, replaced + with phrase and no boolean +- Added debugging + +## 1.2.1 + +- Colorize output +- List all known questions with -l + +## 1.2.0 + +- Add -p switch to pull answer from clipboard when adding a question +- Improve option parsing +- Allow -e when adding answer to open new question file in editor +- Allow multi-line input when adding interactively +- Allow '#atag' to translate to 'tag:atag' in spotlight search +- Allow configuration via environment variables + +## 1.1.1 + +- Only opens url/copies code from one note if there are multiple answers diff --git a/Readme.md b/Readme.md index e82424c..fa12b0a 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,4 @@ -# Quick Question +# Quick Question # [Pretty version](http://ttscoff.github.com/QuickQuestion/) @@ -28,6 +28,23 @@ I use it with nvALT and sync to Simplenote and Dropbox, which means I can enter : By nature, you don't need anything special to use this system with NV. You just need to save your notes to plain text files if you want them to be accessible to `mdfind` and other system tools. : To get simple command line access to the question in your NV folder, set up the configuration in the `qq` script above to point to your NV notes folder. This will allow you to run speedy queries confined to just that directory, and enter notes quickly from other sources. Notes entered from the command line or LaunchBar/Alfred will show up instantly in your NV notes list. + +### CONFIGURATION ### + +Configuration is done via environment variables: + + QQ_NOTES_DIR - Path to Markdown files + QQ_NOTES_EXT - Extension of answer files (default md) + QQ_NOTES_PRE - Prefix of question files (default ??) + QQ_EDITOR - Text editor to use (default $EDITOR) + QQ_USE_FZF - If fzf is available, it will be used by default. Set this + to false to override + + Example: + + export QQ_NOTES_DIR="/Users/ttscoff/Dropbox/Notes" + export QQ_NOTES_EXT="md" + ### Usage ### **Command line** @@ -59,7 +76,7 @@ Keep your questions in a natural language format, but avoid contractions and use When querying, only use operative terms to get the best results. "Where did I leave my glasses" will return poor results if the question you labeled it with was "Where did I put my glasses". Instead, query "where glasses" and you'll find the note instantly. -Running `qq` at the command line with no arguments will list all of the questions and their answers in your archive. This can be handy for using with `grep` to parse out pieces of questions and/or answers that you couldn't find otherwise. +Running `qq -l` at the command line with no arguments will list all of the questions and their answers in your archive. This can be handy for using with `grep` to parse out pieces of questions and/or answers that you couldn't find otherwise. ### Additional meta ### diff --git a/qq b/qq index 5eef6cc..4327f4c 100755 --- a/qq +++ b/qq @@ -1,4 +1,5 @@ #!/bin/bash +QQ_VERSION=1.2.3 # Usage: # Ask a question using natural language # $ qq [search terms] @@ -36,32 +37,14 @@ # export QQ_NOTES_DIR="/Users/ttscoff/Dropbox/Notes" # export QQ_NOTES_EXT="md" # -# CHANGELOG -# 1.2.2 -# - OR query when AND failed was way too broad, replaced -# with phrase and no boolean -# - Added debugging -# -# -# 1.2.1 -# - Colorize output -# - List all known questions with -l -# -# 1.2.0 -# - Add -p switch to pull answer from clipboard when adding a question -# - Improve option parsing -# - Allow -e when adding answer to open new question file in editor -# - Allow multi-line input when adding interactively -# - Allow '#atag' to translate to 'tag:atag' in spotlight search -# - Allow configuration via environment variables -# -# 1.1.1 -# - Only opens url/copies code from one note if there are multiple answers - : ${QQ_NOTES_DIR:="$HOME/Dropbox/Notes/nvALT2.2"} : ${QQ_NOTES_EXT:="md"} : ${QQ_NOTES_PRE:="??"} : ${QQ_EDITOR:=$EDITOR} +_USE_FZF=false +which fzf &>/dev/null +[[ $? == 0 ]] && _USE_FZF=true +: ${QQ_USE_FZF:=$_USE_FZF} _QQ_DEBUG=false @@ -93,7 +76,7 @@ __qq () { OPTIND=1 - while getopts "acdeh?lp" opt; do + while getopts "acdeh?lpv" opt; do case $opt in a) ADDING=true @@ -120,6 +103,10 @@ __qq () { ADDING=true PASTING=true ;; + v) + echo "$0 v$QQ_VERSION" + return + ;; esac done @@ -127,7 +114,7 @@ __qq () { [ "${1:-}" = "--" ] && shift if $HELPING; then - exit 0 + return fi HAS_COPIED_TEXT=false @@ -168,12 +155,13 @@ __qq () { else if [[ $# == 0 ]]; then __qq_help - exit 0 + return fi - QQINPUTQUERY=$(__qq_query_include_all "${*%\?}") + local QQORIGINALQUERY="$*" + local QQINPUTQUERY=$(__qq_query_include_all "${*%\?}") __qq_debug "Attempting to find ALL options: ${QQINPUTQUERY}" QQQUERY="mdfind -onlyin '$NOTESDIR' -interpret '(kind:text OR kind:markdown) AND filename:$NOTESEXT AND filename:$NOTESPRE ${QQINPUTQUERY}${EXCLUDEQQQUERY}'" - RESULTS=$(eval $QQQUERY) + local RESULTS=$(eval $QQQUERY) if [[ "$RESULTS" == "" ]]; then QQINPUTQUERY=$(__qq_query_include_all OR "${*%\?}") @@ -182,41 +170,83 @@ __qq () { RESULTS=$(eval $QQQUERY) fi + if [[ "$RESULTS" == "" ]]; then + + if $QQ_USE_FZF; then + QQQUERY='ls "${NOTESDIR}??"*.$NOTESEXT|fzf -i --tiebreak=length,begin -f "$(__qq_remove_stopwords "$*")"' + __qq_debug "We have fzf, trying: ${QQQUERY}" + RESULTS=$(eval $QQQUERY) + RESULTS=$(echo "$RESULTS" | head -n 1) + fi + fi + + if [[ "$RESULTS" == "" ]]; then + __qq_debug "Well, jeeze, I guess we'll try grepping for the question with the most matching words" + declare -a WORDS=( $* ) + local MAX=${#WORDS} + local RX=$(__qq_query_regex "$*") + for i in $(seq $MAX 2); do + RESULTS=$(ls "${NOTESDIR}??"*.$NOTESEXT|grep -iE "${RX}{$i}") + if [[ "$RESULTS" != "" ]]; then + __qq_debug "Ooh, found a match containing $i of the words: ${RX}{$i}" + break + fi + done + fi + if [[ "$RESULTS" == "" ]]; then echo "$(__qc red)Sorry, I don't know the answer to that question.$(__qc reset)" exit 2 else + # Sort results by length, assuming shortest result is best match + RESULTS=$(echo "$RESULTS" | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-) + + if $QQ_USE_FZF; then + ANSWER=$(echo -e "$RESULTS"|fzf -i --prompt="Select a question > " -1 -q "$QQORIGINALQUERY") + RESULTS="" + else + ANSWER=$(echo -e "$RESULTS" | head -n 1) + RESULTS=$(echo -e "$RESULTS" | sed '1d' | head -n 5) + fi + if $EDITING; then - ANSWER=$(echo "$RESULTS"|head -n 1) + ANSWER=$(echo "$RESULTS" | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2- | head -n 1) $QQEDITOR "$ANSWER" - else - echo -e "$RESULTS" | while read LINE; do - if [[ "$LINE" =~ ^$ ]]; then - echo "$(__qc red)Sorry, I don't know the answer to that question.$(__qc reset)" - exit 1; - fi - QUESTION=`basename "$LINE" ".$NOTESEXT"` - echo -e "\n" - echo -n "$(__qc yellow)Q: $(__qc white)" - NOTESPREESC=`echo "$NOTESPRE"|sed -E 's/([\?\!\$\`\"]) ?/\\\\\1/g'` - echo "$QUESTION"|sed -E "s/$NOTESPREESC ?//g"|sed -E 's/([^\?])$/\1?/' - echo -n "$(__qc yellow)A: $(__qc white)" - cat "$LINE"|sed -E 's/@\([^\)]+\) ?//g'|sed -E 's/@copy\(([^\)]+)\)/\1/'|sed -E 's/@open\(([^\)+]*)\)/Related URL: \1/'|sed -E 's/@[^\( ]+ ?//g' # |sed -E 's/^[ ]*|[ ]*$//g' - if [[ `cat "$LINE"|grep -E '@copy\('` && $HAS_COPIED_TEXT == false ]]; then - cat "$LINE"|grep '@copy('|sed -E 's/.*@copy\(([^\)]+)\).*/\1/'|tr -d '\n'|pbcopy - echo "Example in clipboard" - HAS_COPIED_TEXT=true - fi - - if [[ `cat "$LINE"|grep -E '@open\('` && $HAS_OPENED_URL == false ]]; then - url=$(cat "$LINE"|grep '@open('|sed -E 's/.*@open\(([^\)]+)\).*/\1 /'|tr -d '\n') - open -g $url - echo "Opened URL" - HAS_OPENED_URL=true - fi - done - __qc reset + return fi + + if [[ "$ANSWER" =~ ^$ ]]; then + echo "$(__qc red)Sorry, I don't know the answer to that question.$(__qc reset)" + exit 1; + fi + + QUESTION=`basename "$ANSWER" ".$NOTESEXT"` + echo -e "\n" + echo -n "$(__qc yellow)Q: $(__qc white)" + NOTESPREESC=`echo "$NOTESPRE"|sed -E 's/([\?\!\$\`\"]) ?/\\\\\1/g'` + echo "$QUESTION"|sed -E "s/$NOTESPREESC ?//g"|sed -E 's/([^\?])$/\1?/' + echo -n "$(__qc yellow)A: $(__qc white)" + cat "$ANSWER"|sed -E 's/@\([^\)]+\) ?//g'|sed -E 's/@copy\(([^\)]+)\)/\1/'|sed -E 's/@open\(([^\)+]*)\)/Related URL: \1/'|sed -E 's/@[^\( ]+ ?//g' # |sed -E 's/^[ ]*|[ ]*$//g' + if [[ `cat "$ANSWER"|grep -E '@copy\('` && $HAS_COPIED_TEXT == false ]]; then + cat "$ANSWER"|grep '@copy('|sed -E 's/.*@copy\(([^\)]+)\).*/\1/'|tr -d '\n'|pbcopy + echo -e "\n$(__qc green)Example in clipboard" + HAS_COPIED_TEXT=true + fi + + if [[ `cat "$ANSWER"|grep -E '@open\('` && $HAS_OPENED_URL == false ]]; then + url=$(cat "$ANSWER"|grep '@open('|sed -E 's/.*@open\(([^\)]+)\).*/\1 /'|tr -d '\n') + open -g $url + echo -e "\n$(__qc green)Opened URL" + HAS_OPENED_URL=true + fi + + if [[ "$RESULTS" != "" ]]; then + echo "$(__qc gray)----------------------" + echo "$(__qc yellow)Other results included:" + echo -e "$(__qc cyan)$RESULTS" + fi + + __qc reset fi fi exit 0 @@ -254,6 +284,18 @@ __qq_query_include_all () { fi } +__qq_query_regex () { + if [[ "$*" != "" ]]; then + declare -a query_array=( $* ) + local query="(.*(" + for i in ${query_array[@]}; do + local stripped=$(echo "$i" | sed -E 's/[^A-Z0-9 ]/.?/gi') + query="${query}${stripped}|" + done + echo -n "$query"|sed -e 's/|$//' -e 's/$/).*)/' + fi +} + __qq_query_exclude_all () { local input="$1" local OLDIFS=$IFS