Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: completion of dates



As far as I know, dummy matches are only used in _describe, _values and
_arguments to put matches with a common description together. I wanted
to experiment with it and came up with this. What it does is completion
of days laid out in the form of a calendar. Perhaps a bit of a gimmick
but I think it might be useful, especially in cases where a date is
specified in an obscure way such as for find's -mtime option.

There are some compromises to get this to work:

Cycling matches in order with tab is not correct: matches are added by
row then column rather than in date order.

In order to have labels for months, I've had to use separate groups -
dates-1, dates-2 etc - and a separate _message for the 'date' heading.
There may be combinations of tag-order/group-name styles which don't
work with this.

Weekday labels use dummy matches. These aren't localised because weekday
short forms are 3 characters, not two.

There's a max-matches-length style if you want to limit how much of your
terminal it will use.

There's hard-coded support for advancing to the next set of months if
called from _next_tags.

Getting the padding on the right is really quite tricky. Ideally, it'd
be possible to somehow force the line break. It doesn't manage to be
clever enough to reliably pack the dummy match beween months to be empty
so I've forced that to be two spaces resulting in 6 spaces of padding.
Having a way to reduce the padding around matches in general would allow
it to fit 3 month columns in an 80 character terminal.

I'm not entirely happy with the way it presents days in the future. Menu
selection's support for colouring isn't really flexible enough.

It doesn't parse any partial date you might have entered and with date
formats that include a time, it can be improved (do we want start of day
or whole numbers of days ago, what about at daylight savings?). May also
need an option to use UTC.

I've also patched _find and _globquals to use it. With them, I've
changed the completion of the - and + modifiers to be more compact.

Oliver

diff --git a/Completion/Unix/Command/_find b/Completion/Unix/Command/_find
index 8f80e36..aefca34 100644
--- a/Completion/Unix/Command/_find
+++ b/Completion/Unix/Command/_find
@@ -1,6 +1,7 @@
 #compdef find gfind
 
-local variant args
+local curcontext="$curcontext" state_descr variant
+local -a state line args alts
 
 _pick_variant -r variant gnu=GNU $OSTYPE -version
 
@@ -114,11 +115,11 @@ case $variant in
   ;;
 esac
 
-_arguments $args \
+_arguments -C $args \
   '(-L -P)-H[only follow symlinks when resolving command-line arguments]' \
   '(-H -P)-L[follow symlinks]' \
-  '*-atime:access time (days)' \
-  '*-ctime:inode change time (days)' \
+  '*-atime:access time (days):->times' \
+  '*-ctime:inode change time (days):->times' \
   '*-depth' \
   '*-exec:program: _command_names -e:*\;::program arguments: _normal' \
   '*-follow' \
@@ -128,7 +129,7 @@ _arguments $args \
   '*-links:number of links:' \
   '*-ls' \
   '*-mount' \
-  '*-mtime:modification time (days)' \
+  '*-mtime:modification time (days):->times' \
   '*-name:name pattern' \
   '*-newer:file to compare (modification time):_files' \
   '*-nogroup' \
@@ -143,3 +144,13 @@ _arguments $args \
   '*-xdev' \
   '*-a' '*-o' \
   '*:directory:_files -/'
+
+if [[ $state = times ]]; then
+  if ! compset -P '[+-]' || [[ -prefix '[0-9]' ]]; then
+    disp=( 'before' 'exactly' 'since' )
+    compstate[list]+=' packed'
+    alts=( "senses:sense:compadd -V times -S '' -d disp - + '' -" )
+  fi
+  alts+=( "times:${state_descr}:_dates -f d" )
+  _alternative $alts
+fi
diff --git a/Completion/Unix/Type/_dates b/Completion/Unix/Type/_dates
new file mode 100644
index 0000000..486a2c2
--- /dev/null
+++ b/Completion/Unix/Type/_dates
@@ -0,0 +1,123 @@
+#autoload
+
+# Options:
+#  -f format      : specify strftime format for matches
+#  -f s/m/h/d/w/M : specify relative format
+#  -F             : select a future rather than past date
+
+# Styles:
+#   max-matches-length : maximum number or percentage of lines to use for
+#                        completion listing, if both are specified, the
+#                        lowest takes precedence.
+#   format             : override date format
+
+local -a disp cand expl
+local userformat format spacer=1 spacing month monstart skip match
+local d day daysecs extra preclude r ri col explain
+local -a starts skips
+local -i start now mult
+local -i columns=$(( (COLUMNS+4) / 32 )) rows=LINES-4 offset=0
+local -a days=( Mo Tu We Th Fr Sa Su )
+local future mlabel mfmt mlabels
+
+zparseopts -D -K -E f:=format F=future X:=explain
+(( future = $#future ? 1 : -1 ))
+zstyle -s ':completion:$curcontext:dates' date-format userformat
+format=${userformat:-${format[2]:-%F}}
+
+zstyle -a ':completion:$curcontext:dates' max-matches-length r
+for ri in $r; do
+  [[ $ri = [0-9]##% ]] && (( ri = LINES * .${ri%%%} ))
+  (( ri < rows )) && (( rows=ri ))
+done
+(( rows = rows / 8 ))
+zmodload -i zsh/datetime || rows=0
+
+_message -e dates ${explain[2]:-date}
+(( rows )) || return
+_description -2V -x dates expl date
+compstate[list]='packed rows'
+
+if [[ $WIDGET = _next_tags ]]; then
+  typeset -g -i _next_tags_line
+  typeset -g -i _next_tags_date=$(( HISTNO == _next_tags_line ? _next_tags_date+1 : 1))
+  _next_tags_line=HISTNO
+  (( offset = _next_tags_date*rows*columns ))
+fi
+
+(( now=EPOCHSECONDS ))
+strftime -s year '%Y' $now
+strftime -s month '%m' $now
+(( offset = future*offset + year*12 + month + ((future == 1) ? rows*columns-2  : -1) ))
+for ((;rows;rows--)); do
+  disp=() mlabels=""
+  for ((col=1;col<=columns;col++)); do
+    (( start = offset + col - rows * columns ))
+    strftime -r -s monstart '%Y%m' $(( start/12 ))$(( 1 + start % 12 ))
+    strftime -s skip '%w' $(( monstart-86400 ))
+    starts[col]=$monstart
+    skips[col]=$skip
+    disp+=( $days '  ' )
+
+    mfmt='%B'
+    strftime -s mlabel '%m' $monstart
+    [[ $mlabel = 01 ]] && mfmt+=' %Y'
+    strftime -s mlabel "$mfmt"  $monstart
+
+    mlabels+="${(r.(col == columns) ? 28 : 32.):-${(l.(26-$#mlabel)/2.)}$mlabel}"
+  done
+  (( spacing = COLUMNS - 32 * columns + 2 ))
+  disp[-1]="${(l.spacing.)}"
+  (( spacing < 2 )) && spacer=0 disp[-1]=()
+  expl[1+expl[(i)-V]]=dates-$rows
+  compadd -x "$mlabels" "$expl[@]" -d disp -E $(( $#disp ))
+
+  for ((line=0;line<6;line++)); do
+    for ((col=1;col<=columns;col++)); do
+      if (( skips[col] && !line )); then
+	disp=(); disp[skips[col]]=''
+	compadd -x "$mlabels" "$expl[@]" -d disp -E $skips[col]
+	(( skip=skips[col] ))
+      else
+	skip=0
+      fi
+      disp=() cand=()
+      (( extra = (col == columns) ? spacer : 1 ))
+      (( preclude = 0 ))
+      for ((d=1;d<=7-skip;d++)); do
+	(( day = d+7*line+skip-skips[col] ))
+	(( daysecs = starts[col] + 86400 * (day - 1) ))
+	strftime -s realday '%d' $daysecs
+	if (( realday != day )); then
+	  (( extra+=8-d ))
+	  break
+	fi
+	(( mult = -future * (now - daysecs) + (future == 1 ? 86400 : 0) ))
+	case $format in
+	  s) (( match = mult )) ;;
+	  m) (( match = mult / 60 )) ;;
+	  h) (( match = mult / 3600 )) ;;
+	  d) (( match = mult / 86400 )) ;;
+	  w) (( match = mult / 604800 )) ;;
+	  M) (( match = mult / 2592000 )) ;;
+	  *) strftime -s match - $format $daysecs ;;
+	esac
+	disp+=( "${(l.2.)day}" )
+	if (( future < 0 && now < daysecs )); then
+	  (( extra++ ))
+	elif (( future > 0 && (now - daysecs) > 86400 )); then
+	  (( preclude++ ))
+	else
+	  (( (now - daysecs) < 86400 && (now - daysecs) > 0 )) &&
+	      compstate[insert]=menu:$(( compstate[nmatches] + $#disp ))
+	  cand+=( "$match" )
+	fi
+      done
+      if (( preclude )); then
+	compadd -x "$mlabels" "$expl[@]" -E $preclude -d disp
+	shift preclude disp
+      fi
+      compadd -x "$mlabels" -U -i "$IPREFIX" -I "$ISUFFIX" "$expl[@]" "$@" -d disp -E $extra -a cand
+    done
+  done
+done
diff --git a/Completion/Zsh/Type/_globquals b/Completion/Zsh/Type/_globquals
index 042b274..5cdb8f7 100644
--- a/Completion/Zsh/Type/_globquals
+++ b/Completion/Zsh/Type/_globquals
@@ -1,7 +1,7 @@
 #autoload
 
 local state=qual expl char delim timespec
-local -a alts
+local -a alts tdisp sdisp
 local -A specmap
 
 while [[ -n $PREFIX ]]; do
@@ -117,14 +117,15 @@ while [[ -n $PREFIX ]]; do
       alts=()
       timespec=$PREFIX[1]
       if ! compset -P '[Mwhmsd]' && [[ -z $PREFIX ]]; then
-        alts+=("time-specifiers:time specifier:\
-((M\:months w\:weeks h\:hours m:\minutes s\:seconds d\:days))")
+	tdisp=( seconds minutes hours days weeks Months )
+        alts+=( "time-specifiers:time specifier:compadd -E 0 -d tdisp -S '' - s m h d w M" )
       fi
       if ! compset -P '[-+]' && [[ -z $PREFIX ]]; then
-        alts+=("senses:sense:((-\:less\ than +\:more\ than))")
+	sdisp=( before exactly since )
+        alts+=("senses:sense:compadd -E 0 -d sdisp -S '' - + '' -")
       fi
       specmap=( M months w weeks h hours m minutes s seconds '(|+|-|d)' days)
-      alts+=('digits:digit ('${${specmap[(K)$timespec]}:-invalid time specifier}'):' )
+      alts+=('digits:digit ('${${specmap[(K)$timespec]}:-invalid time specifier}'):_dates -f ${${timespec/[-+]/d}:-d} -S ""' )
       _alternative $alts
       return
     fi



Messages sorted by: Reverse Date, Date, Thread, Author