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

Re: OpenStack CLI completion



Hey,

> I have a vague recollection that some (sub)command(s) had a slightly
> inconsistent output with colons somewhere which had to be removed for
> completions. Could have been just one or two such commands but
> unfortunately I don't remember exactly anymore.

@Marko, I tried all clients I could find easily and could not find any
suggestions including colons. I cannot handle an error I don't know how to
throw. Please let me know if you remember an example.

> > * Why is there a check for not prefix-needed?
> >
> > Some comments from the original author would be quite helpful if he
> > still remembers why it was done a certain way :)
>
> Hmm, this one I don't remember, I guess this might be a common
> convention or something like that, probably not specific to _openstack.

I read up on it and I think it has something to do with how people configure
their completion preferences with `zstyle`. If you are a nice completion writer
you honor the setting. I hope I did it justice.

> I haven't dealt with OpenStack recently so I can't actually test
> anything around it anymore but if you could fix the issue that would be
> great.

I gave it a try. I ignored any colon issues for now. Would be great if someone
could.

So @list what do I need to do to get this merged?
From ecb7cbe49bce946bb4d6e919794af72f3d43014d Mon Sep 17 00:00:00 2001
From: Syphdias <syphdias+git@xxxxxxxxx>
Date: Thu, 18 Mar 2021 23:23:16 +0100
Subject: [PATCH] Fix _openstack completion for new style clients
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

I have rewritten the completion function for new style clients. It no longer
differentiates between command and sub command since this lead to a limited
depth for completion lookups. This gets rid of the extra global caching variable
`_cache_openstack_clnt_cmds`.  I tried to stay true to the prefix-needed
setting.

First I process the words left of the cursor. `words` did not provide this
granularity I needed so I opted to parse `LBUFFER` also for saving a partial
input/match for later. I remove `help` since everything after it is normal (sub)
command, so matches are identical to proper commands. Also I filter out every
flag/option.

To find the proper completion options I try one level at a time.
* $service
* $service $some_command
* $service $some_command $some_command_for_some_command
* etc.
You could probably do this in reverse to save time but I don't think it is worth
the effort.

I add the global options if `-` is used as a prefix at the current position.

Caveats:
* I know there are options like `--file`. The new implementation does not handle
  this – neither did the old one. For this level, I'd suggest the OpenStack team
  to provide official zsh completions
* Ignores everything right of the cursor so you can end up with command
  suggestion that are already on the right
* `openstack complete` gets completed now, old implementation ignored it
---
 Completion/Unix/Command/_openstack | 119 +++++++++++++++--------------
 1 file changed, 63 insertions(+), 56 deletions(-)

diff --git a/Completion/Unix/Command/_openstack b/Completion/Unix/Command/_openstack
index fcb704ac8..c12f25985 100644
--- a/Completion/Unix/Command/_openstack
+++ b/Completion/Unix/Command/_openstack
@@ -34,8 +34,6 @@ if (( ! $+_cache_openstack_clnt_opts )); then
   typeset -gA _cache_openstack_clnt_opts
   typeset -gA _cache_openstack_clnt_cmds
   typeset -gA _cache_openstack_clnt_cmds_opts
-  typeset -gA _cache_openstack_clnt_cmds_subcmds
-  typeset -gA _cache_openstack_clnt_cmds_subcmd_opts
 fi
 
 local -a conn_opts
@@ -61,65 +59,74 @@ if [[ -n ${clnts_compl_new[(r)$service]} ]]; then
     # Populate caches - clnt_outputs is command raw output used later
     _cache_openstack_clnt_outputs[$service]=${:-"$($service ${(Q)conn_opts} complete 2>/dev/null)"}
     _cache_openstack_clnt_opts[$service]=${${${${(M)${${${${=${(f)"$($service help 2>/dev/null)"}}/\[}/\]}/\;}:#-[-0-9A-Za-z]*}/,}/\.}%--os-}
-    _cache_openstack_clnt_cmds[$service]=${${${${_cache_openstack_clnt_outputs[$service]}/* cmds=\'}/\'*}/complete}
   fi
-  local cmd subcmd
-  # Determine the command
-  for word in ${words:1}; do
-    local s=${_cache_openstack_clnt_cmds[$service]}
-    [[ $s[(wI)$word] -gt 0 ]] && cmd=$word && break
+
+  # get worlds left of the curser into an array
+  local -a left_words
+  left_words=(${=LBUFFER})
+
+  # if curser is directly at a word (no space at the end),
+  # exclude the last word to offer right matches
+  # the last word could be a partial match that is later checked (prefix-needed)
+  local partial=""
+  if [[ "${LBUFFER[-1]}" != " " ]]; then
+    partial=${(@)left_words[-1]}
+    left_words=(${(@)left_words[1,$#left_words-1]})
+  fi
+  # remove $service
+  left_words=(${left_words:1})
+
+  # filter out "help"
+  if [[ $left_words[1] == help ]]; then
+    left_words=(${(@)left_words[2,$#left_words]})
+  fi
+
+  # filter out options (-*)
+  left_words=(${left_words//-*})
+
+  local -a subcmd_array cmds_array cache_key_array cache_values
+  subcmd_array=()
+  cmds_array=(cmds)
+  cache_key_array=(${service})
+  cache_values=()
+  local cache_key cmds
+  cache_key=""
+  cmds=""
+
+  # Check for matches one level at a time
+  # example: "" server create
+  for word in "" ${(@)left_words}; do                   # first loop  second loop        third loop
+    subcmd_array=(${(@)subcmd_array} ${word})           # ()          (server)           (server create)
+    cmds_array=(${(@)cmds_array} ${word})               # (cmds)      (cmds server)      (cmds server create)
+    cmds=${${(j:_:)cmds_array}/-/_}                     #  cmds        cmds_openstack     cmds_server_create
+    cache_key_array=(${(@)cache_key_array} ${word})     # (openstack) (openstack server) (openstack server create)
+    cache_key=${${(j:_:)cache_key_array}/-/_}           #  openstack   openstack_server   openstack_server_create
+
+    # lookup if current word is in cache_values of last elements
+    if [[ ${cache_values[(wI)${word}]} -gt 0 || $word == "" ]]; then
+      _cache_openstack_clnt_cmds[${cache_key}]=${${${_cache_openstack_clnt_outputs[${service}]}/* ${cmds}=\'}/\'*}
+    fi
+    # set cache_values for next loop
+    cache_values=${_cache_openstack_clnt_cmds[${cache_key}]}
   done
-  # Populate the subcommand cache
-  if [[ -n $cmd && -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]]; then
-      local t=cmds_${cmd//-/_}
-      _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*}
-  fi
-  # Determine the subcommand
-  if [[ -n $cmd ]]; then
-    for word in ${words:2}; do
-      local s=${_cache_openstack_clnt_cmds_subcmds[$service$cmd]}
-      [[ $s[(wI)$word] -gt 0 ]] && subcmd=$word && break
-    done
-    # Populate subcommand option cache
-    if [[ -n $subcmd && -z $_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd] ]]; then
-      local t=cmds_${cmd//-/_}_${subcmd//-/_}
-      _cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*}
-    fi
-  fi
-  # Special treatment for the help command
-  if [[ $cmd == help ]]; then
-      if [[ $words[CURRENT-1] == $cmd && $words[CURRENT] != -* ]]; then
-        # Offer commands
-        [[ -n $_cache_openstack_clnt_cmds[$service] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
-      elif [[ $words[CURRENT-2] == $cmd && $words[CURRENT-1] != -* && $words[CURRENT] != -* ]]; then
-        # Offer subcommands
-        local cmd=$words[CURRENT-1]
-        local t=cmds_${cmd//-/_}
-        [[ -z $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _cache_openstack_clnt_cmds_subcmds[$service$cmd]=${${${_cache_openstack_clnt_outputs[$service]}/* $t=\'}/\'*}
-        [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0
-      else
-        # Handle help<TAB> properly
-        _values -w option help && ret=0
-      fi
-  # Client options
-  elif [[ -z $cmd && $words[CURRENT] == -* ]]; then
-    _values -w option ${(u)=_cache_openstack_clnt_opts[$service]} && ret=0
-  # Commands
-  elif [[ -z $cmd ]]; then
-    if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
-      _message "missing authentication options"
-    else
-      _values -w option ${(u)=_cache_openstack_clnt_cmds[$service]} && ret=0
-    fi
-  # Subcommands
-  elif [[ -z $subcmd ]]; then
-    [[ -n $_cache_openstack_clnt_cmds_subcmds[$service$cmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmds[$service$cmd]} && ret=0
-  # Subcommand options
+
+  # Populate the command cache
+  if [[ -z $_cache_openstack_clnt_cmds[${cache_key}] ]]; then
+    _message "missing authentication options"
   else
-    { ! zstyle -T ":completion:${curcontext}:options" prefix-needed || [[ -prefix - ]] } && \
-      [[ -n $_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd] ]] && _values -w option ${(u)=_cache_openstack_clnt_cmds_subcmd_opts[$service${cmd}--$subcmd]//\:/\\\:} && ret=0
+    # add global options to completion list if current word start with -*
+    local extra_opts
+    if [[ $words[CURRENT] == -* ]]; then;
+      extra_opts=${_cache_openstack_clnt_opts[$service]}
+    fi
+
+    { ! zstyle -T ":completion:${curcontext}:options" prefix-needed \
+          || [[ -n "${partial}" && ${${_cache_openstack_clnt_cmds[${cache_key}]}[(Iw)${partial}*]} -gt 0 || -prefix - ]] } \
+      && _values -w option ${(u)=_cache_openstack_clnt_cmds[${cache_key}]} ${(u)=extra_opts} \
+      && ret=0
   fi
 
+
 # Old style clients
 elif [[ -n ${clnts_compl_old[(r)$service]} ]]; then
   if [[ -z $_cache_openstack_clnt_cmds[$service] ]]; then
-- 
2.30.1



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