Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General improvement to ZSH completion + bug fix in it #357

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
224 changes: 84 additions & 140 deletions contrib/completion/zsh/_todo
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@

# {{{ sub commands common options variables
local common_options_help=(
'(- :)--help[Show a help message and exit]'
'(- :)--help[show a help message and exit]'
)
local common_options_start=(
{-s,--start=}'[When the task starts]:DATE:__todo_date'
{-s,--start=}'[when the task starts]:date:__todo_date'
)
local common_options_due=(
{-d,--due=}'[When the task is due]:DATE:__todo_date'
{-d,--due=}'[when the task is due]:date:__todo_date'
)
local common_options_priority=(
'--priority=[The priority for this todo]:PRIORITY:("low" "medium" "high")'
'--priority=[the priority for this todo]:priority:("low" "medium" "high")'
)
local common_options_interactive=(
{-i,--interactive}'[Go into interactive mode before saving the task]'
{-i,--interactive}'[go into interactive mode before saving the task]'
)
local common_options_location=(
'--location=[The location where this todo takes place]:LOCATION:'
'--location=[the location where this todo takes place]:location:'
)
# }}}
# {{{ option helper: color mode
Expand All @@ -30,150 +30,94 @@ __color_mode(){
_describe "mode" modes
}
# }}}
# {{{ general helper: set variable of path to configuration file
__todo_set_conf(){
todoman_configuration_file=${XDG_CONFIG_DIR:-${HOME}/.config}/todoman/todoman.conf
if [[ -f $todoman_configuration_file ]]; then
return 0
else
return 1
fi
}
# }}}
# {{{ general helper: set variable main.path from configuration file
__todo_set_conf_path(){
if __todo_set_conf; then
tasks_lists_path="$(sed -n -e 's/^[^#]\s*path\s*=\s*\(.*\)$/\1/p' $todoman_configuration_file 2>/dev/null)"
# the eval echo is needed since the path may contain ~ which should be evalueated to $HOME
tasks_lists_dir="$(eval echo ${tasks_lists_path%/\**})"
if [[ -z "${tasks_lists_path}" || ! -d "${tasks_lists_dir}" ]]; then
return 1
else
return 0
fi
else
return 1
fi
}
# }}}
# {{{ general helper: set variables related to date and time formats for __todo_date
__todo_set_conf_dt(){
if __todo_set_conf; then
date_format="$(eval echo $(sed -n -e 's/^[^#]\s*date_format\s*=\s*\(.*\)$/\1/p' $todoman_configuration_file 2>/dev/null))"
dt_separator="$(eval echo $(sed -n -e 's/^[^#]\s*dt_separator\s*=\s*\(.*\)$/\1/p' $todoman_configuration_file 2>/dev/null))"
time_format="$(eval echo $(sed -n -e 's/^[^#]\s*time_format\s*=\s*\(.*\)$/\1/p' $todoman_configuration_file 2>/dev/null))"
# default value according to documentation: https://todoman.readthedocs.io/en/stable/configure.html
if [[ -z "${date_format}" ]]; then
date_format="%x"
fi
if [[ -z "${dt_separator}" ]]; then
dt_separator=""
fi
if [[ -z "${time_format}" ]]; then
time_format="%x"
fi
return 0
else
return 1
fi
}
# }}}
# {{{ option helper: due and start date
__todo_date(){
if __todo_set_conf_dt; then
_message "date in format ${date_format//\%/%%}${dt_separator//\%/%%}${time_format//\%/%%}"
else
_message "date format (couldn't read configuration file and extract date and time formats)"
typeset -A date_time_dict
# setting defaults so no matter if these keys will be parsed from config,
# we'll have some values here anyway
date_time_dict[date_format]='%x'
date_time_dict[dt_separator]=' '
date_time_dict[time_format]='%X'
# parse configuration file to get date and time formats from there
local conf_file="${XDG_CONFIG_DIR:-${HOME}/.config}/todoman/todoman.conf"
if [[ -f "$conf_file" ]]; then
while read line; do
for key in date_format dt_separator time_format; do
if [[ "$line" =~ "^\s*$key\s*=\s*" ]]; then
line="${line[$MEND+1,${#line}]}"
date_time_dict[$key]="$line"
fi
done
done < "$conf_file"
fi
# substitute '%' from values so it won't get messy
local date_format="${date_time_dict[date_format]//\%/%%}"
local time_format="${date_time_dict[time_format]//\%/%%}"
local dt_separator="${date_time_dict[dt_separator]}"
_message -r "date in format ${date_format}${dt_separator}${time_format}"
}
# }}}
# {{{ argument helper: sub-command choice
__todo_command(){
local commands=(
'cancel:Cancel one or more tasks'
'copy:Copy tasks to another list'
'delete:Delete tasks'
'done:Mark one or more tasks as done'
'edit:Edit the task with id ID'
'flush:Delete done tasks'
'list:List tasks'
'move:Move tasks to another list'
'new:Create a new task with SUMMARY'
'show:Show details about a task'
'cancel:cancel one or more tasks'
'copy:copy tasks to another list'
'delete:delete tasks'
'done:mark one or more tasks as done'
'edit:edit the task with id ID'
'flush:delete done tasks'
'list:list tasks'
'move:move tasks to another list'
'new:create a new task with SUMMARY'
'show:show details about a task'
)
_describe "command" commands
}
# }}}
# {{{ argument helper: available tasks choice
__todo_tasks(){
# checking if the command jq exists and it's version
# credit: http://stackoverflow.com/a/592649/4935114
jq_version=$(jq --version 2>/dev/null)
if [ ${${jq_version#jq\-}//./} -lt 15 ]; then
_message "we can't complete tasks unless you'll install the latest version of jq: https://stedolan.github.io/jq/"
return
fi
# $1 is a comma-seperated list of statuses to show when trying to complete this
local status_search_query="$1"
local requested_status="$1"
local -a tasks
IFS=$'\n'
for task_and_description in $(todo --porcelain list --status "${status_search_query}" | jq --raw-output '.[] | .id,":\"@",.list," ",.summary,"\"\\0"' | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' -e 's/\\0/\n/g'); do
tasks+="$(eval echo ${task_and_description})"
local i st text
todo list --status="$requested_status" | while read i st_text; do
tasks+=("$i":"$st_text")
done
_describe tasks tasks
}
# }}}
# {{{ todo available lists cache policy
__todo_lists_cache_policy(){
# the number of seconds since 1970-01-01 the directory
local tasks_lists_dir_last_date_modified="$(date -r ${tasks_lists_dir} +%s 2>/dev/null)"
# the number of seconds since 1970-01-01 the cache file was modified
local cache_last_date_modified="$(date -r $1 +%s 2>/dev/null)"
if [[ ! -z ${cache_last_date_modified} && ! -z ${tasks_lists_dir_last_date_modified} ]]; then
# if the manifest file is newer then the cache:
if [ ${tasks_lists_dir_last_date_modified} -ge ${cache_last_date_modified} ]; then
(( 1 ))
else
(( 0 ))
fi
else
(( 1 ))
fi
}
# }}}
# {{{ option helper: available lists
__todo_lists(){
if __todo_set_conf_path; then
local update_policy
zstyle -s ":completion:${curcontext}:" cache-policy update_policy
if [[ -z "$update_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy __todo_lists_cache_policy
fi
local -a tasks_lists
if _cache_invalid todoman_lists; then
if [[ ${tasks_lists_path} =~ '/*$' ]]; then
for dir in $(eval echo ${tasks_lists_path}); do
if grep "VTODO" -q -R "${dir}"; then
list_name="${dir##*/}"
tasks_lists+=("${list_name}")
fi
done
local conf_file="${XDG_CONFIG_DIR:-${HOME}/.config}/todoman/todoman.conf"
local tasks_path
if [[ -f "$conf_file" ]]; then
# parse configuration file to get where the tasks are stored (main.path)
while read line; do
if [[ "$line" =~ '^\s*path\s*=\s*' ]]; then
tasks_path=("${line[$MEND+1,${#line}]}")
break
fi
_store_cache todoman_lists tasks_lists
else
_retrieve_cache todoman_lists
fi
if [[ "${#tasks_lists[@]}" == 1 ]]; then
_message "only one list was detected: (\"${tasks_lists[1]}\")"
return
else
_describe "available lists" tasks_lists
return
done < "$conf_file"
if [ -z "${tasks_path+1}" ]; then
_message -e "no 'path = ' string was found in todoman's default configuration file ($todoman_configuration_file)"
return 1
fi
else
_message -e "no 'path = ' string was found in todoman's default configuration file ($todoman_configuration_file)"
return
_message -r "can't display tasks' lists since completion function didn't find the configuration file at $todoman_configuration_file"
return 1
fi
local -a actual_suggestions
# expand manually * found in tasks_paths array
local expanded_tasks_paths=(${~tasks_path})
local expanded_tasks_path
for expanded_tasks_path in "${expanded_tasks_paths[@]}"; do
if [[ -r "${expanded_tasks_path}/displayname" ]]; then
actual_suggestions+=("$(< "${expanded_tasks_path}/displayname")")
elif [[ -d "${expanded_tasks_path}" ]]; then
# This leaves only the tail of the expanded path
actual_suggestions+=("${expanded_tasks_path:t}")
fi
done
_describe -t values list actual_suggestions
}
# }}}
# {{{ command `cancel`
Expand All @@ -186,7 +130,7 @@ _todo_cancel(){
# {{{ command `copy`
local _command_copy_options=(
"${common_options_help[@]}"
{-l,--list=}'[The list to copy the tasks to]:TEXT:__todo_lists'
{-l,--list=}'[the list to copy the tasks to]:text:__todo_lists'
)
_todo_copy(){
_arguments \
Expand All @@ -197,7 +141,7 @@ _todo_copy(){
# {{{ command `delete`
local _command_delete_options=(
"${common_options_help[@]}"
"--yes[Don't ask for permission before deleting]"
"--yes[don't ask for permission before deleting]"
)
_todo_delete(){
_arguments \
Expand Down Expand Up @@ -237,15 +181,15 @@ _todo_flush(){
# {{{ command `list`
_command_list_options=(
"${common_options_location[@]}"
'--category=[Only show tasks with category containg TEXT]:TEXT:__todo_existing_categories'
'--grep=[Only show tasks with message containg TEXT]:TEXT:'
'--sort=[Sort tasks using these fields]:TEXT:(description location status summary uid rrule percent_complete priority sequence categories completed_at created_at dtstamp start due last_modified)'
'--category=[only show tasks with category containg TEXT]:text:__todo_existing_categories'
'--grep=[only show tasks with message containg TEXT]:text:'
'--sort=[sort tasks using these fields]:text:(description location status summary uid rrule percent_complete priority sequence categories completed_at created_at dtstamp start due last_modified)'
'(--reverse --no-reverse)'{--reverse,--no-reverse}'[sort tasks in reverse order (see --sort)]'
"${common_options_start[@]}"
"${common_options_due[@]}"
'--priority[Only show tasks with priority at least as high as TEXT]:TEXT:("low", "medium", "high")'
'--startable[Show only todos which should can be started today]'
{-s,--status=}'[Show only todos with the provided comma-separated statuses]:STATUS:{_values -s , "status" "NEEDS-ACTION" "CANCELLED" "COMPLETED" "IN-PROCESS" "ANY"}'
"${common_options_priority[@]}"
'--startable[show only todos which should can be started today]'
{-s,--status=}'[show only todos with the provided comma-separated statuses]:status:("NEEDS-ACTION" "CANCELLED" "COMPLETED" "IN-PROCESS" "ANY")'
"${common_options_help[@]}"
)
_todo_list(){
Expand All @@ -264,8 +208,8 @@ local _command_new_options=(
"${common_options_start[@]}"
"${common_options_due[@]}"
"${common_options_help[@]}"
{-l,--list=}'[The list to move the tasks to]:TEXT:__todo_lists'
'--location[The location where this todo takes place.]:TEXT:__todo_existing_locations'
{-l,--list=}'[the list to move the tasks to]:text:__todo_lists'
'--location[the location where this todo takes place.]:location:'
"${common_options_priority[@]}"
"${common_options_interactive[@]}"
)
Expand All @@ -283,11 +227,11 @@ _todo_show(){

# The real thing
_arguments -C -A "-*" \
{-v,--verbosity=}'[Set verbosity to the given level]:MODE(CRITICAL ERROR WARNING INFO DEBUG)' \
'--color=[Set colored output mode]:MODE:__color_mode' \
'--porcelain[Use a JSON format that will remain stable regadless of configuration or version]' \
{-h,--humanize}'[Format all dates and times in a human friendly way]' \
'(- :)--version[Show the version and exit]' \
{-v,--verbosity=}'[set verbosity to the given level]:mode:(CRITICAL ERROR WARNING INFO DEBUG)' \
'--color=[set colored output mode]:mode:__color_mode' \
'--porcelain[use a JSON format that will remain stable regadless of configuration or version]' \
{-h,--humanize}'[format all dates and times in a human friendly way]' \
'(- :)--version[show the version and exit]' \
"${common_options_help[@]}" \
'1: :__todo_command' \
'*::arg:->args'
Expand Down