about summary refs log tree commit diff
path: root/Etc/completion-style-guide
diff options
context:
space:
mode:
Diffstat (limited to 'Etc/completion-style-guide')
-rw-r--r--Etc/completion-style-guide426
1 files changed, 388 insertions, 38 deletions
diff --git a/Etc/completion-style-guide b/Etc/completion-style-guide
index 307954760..d57a1a7fb 100644
--- a/Etc/completion-style-guide
+++ b/Etc/completion-style-guide
@@ -1,44 +1,394 @@
-For now this is just a list of things one should or shouldn't do.
-
-1)  Use the functions `_files' and `_path_files' instead of `compgen'
-    with the `-f', `-/', or `-g' options.
-2)  *Never* use `compgen' with the `-s' option. This can always be done 
-    by a call to `compadd' which is faster.
-3)  Using `compgen' with the `-k' option should only be done if a) the
-    array is already existent or b) it is very large (several hundred
-    or thousend elements). In other cases using `compadd' is faster.
-4)  Supply match specifications to `compadd' and `compgen' if there are 
-    sensible ones.
-5)  Use `_description' when adding matches with `compadd' or
-    `compgen'. Use `_message' in places where no matches can be
-    generated. If you want to add different types of matches, add them
-    with multiple calls to `compadd' or `compgen', supplying different
-    descriptions.
-6)  Use helper functions that do option completion for you (like
-    `_arguments' and `_long_options') -- it will make your life much
+Contexts, tags and all that
+---------------------------
+
+The completion system keeps track of the current context in the
+parameter `curcontext'. It's content is the hierarchical name for the
+current context sans the `:completion:' and the last colon and the tag
+currently tried. The tags represent different types of matches. So,
+whenever you are about to add matches, you should use a tag for them
+and test if the user wants this type of matches to be generated.
+However, this only really needs to be done if no other function in the
+call chain has tested that already or if you can offer different types
+of matches or if you can handle tag aliases in some sophisticated way.
+
+Most of the utility functions do the testing themselves, so you don't
+have to worry about that at all. For example if you are adding matches 
+with `_files', `_hosts' or functions like these, you can just call
+them and they do the tests needed and the loops over the tag aliases.
+The functions `_arguments' and `_values' do that too, but there is a
+small difference. These functions effectively change the context
+name and if you are using the `->state' form for actions, this changed
+name component has to be reported back to the function calling
+`_arguments' or `_values'. This is done with the parameter `context',
+so you have to make that local in the calling function in the same way
+as you have to make local `line', `state', and `{opt,val}_args'. This
+parameter `context' should then be used when you start adding matches
+by giving it to functions like `_tags' via the `-C' options, as in:
+
+  local context ...
+  ...
+  _arguments ... '-foo:foo:->foo'
+  ...
+  if [[ "$state" = foo ]]; then
+    _tags -C "$context" ...
+    ...
+  fi
+
+This will put the context name given in the argument field of the
+`curcontext' parameter and this context will then be used to look 
+up styles for the tags.
+
+But since this is often used, `_arguments' and `_values' have support
+to make your life easier in such cases. With the `-C' option, these
+functions set the parameter `curcontext', thus modifying the globally
+used hierarchical context name. This means, that you have to make that 
+local, but then you don't have to worry about giving the context name
+reported back to functions you call. E.g.:
+
+  local curcontext="$curcontext" ...
+  ...
+  _arguments -C ... 'foo:foo:->foo'
+  ...
+  if [[ "$state" = foo ]]; then
+    _tags ...
+    ...
+  fi
+
+In this case the parameter `context' is not set, so you don't have to
+make that local. But make sure that `curcontext' is local so that the
+value changed by `_arguments' and `_values' is only used in your
+function (and make sure to initialise it to its old value as in the
+example).
+
+Then, before adding the matches, see if matches of that type are
+requested by the user in the current context. If you will add only one 
+type of matches, this is very simple. You can use the function
+`_wanted' for this. Its return value is zero only if the type of
+matches is requested by the user, so you can just do:
+
+  _wanted names || return 1
+
+  _all_labels names expl 'name' compadd - alice bob
+
+The `_all_labels' function implements the loop over the tag aliases and
+handles the user-defined description, using (in the example) the
+parameter `expl' to store options to give to the command. These option 
+are inserted into the command line either directly before a single
+hyphen if there is such an argument or after the first word if there
+is no single hyphen. Since using `_all_labels' is so much more conveient
+than writing the loop with the `_next_label' function (see below), but
+some function called to generate matches don't accept a single hyphen
+as argument anywhere but want the options built as their last arguments,
+`_all_labels' will *replace* the hyphen with the options if the hyphen is
+the last argument. A good example for such a function is
+`_combination' which can be called like:
+
+  _all_labels foo expl 'descr...' _combination ... -
+
+And the `-' will be replaced by the options that are to be given to
+`compadd'.
+
+Since the above sequence of command is used so often, the `_wanted'
+function can also accept the same arguments as `_all_labels'. In this
+case it will do the test for the requested tag and then just call
+`_all_labels', so:
+
+  _wanted names expl 'name' compadd - alice bob
+
+Note that you can also give the `-J' and `-V' options with the
+optional `1' or `2' preceding them supported by `_description':
+
+  _wanted -2V names expl 'name' compadd ...
+
+In some cases one needs to call multiple functions or call `compadd'
+more than once to generate the matches. In such a case one needs to
+implement the loop over the tag aliases directly. This is done with the 
+`_next_label' function. Like this:
+
+  while _next_label names expl 'name'; do
+    compadd "$expl[@]" - alice bob && ret=0
+    _other_names "$expl[@]" && ret=0
+  done
+  return ret
+
+Simple enough, I hope. But `_next_label' can do some more: utility
+functions normally accept options which are then given to `compadd'.
+Since these may contain options for the description and `_next_label' may
+generate such options, too, it isn't entirely trivial to decide which
+of these options should take precedence. But `_next_label' can do the work
+for you here. All you have to do is to give the options your utility
+function gets to `_next_label', as in:
+
+  while _next_label names expl 'name' "$@"; do
+    compadd "$expl[@]" - alice bob
+    ...
+  done
+
+That's all. Note that the positional argument "$@" are *not* given to
+`compadd'. They will be stuffed into the `expl' array by `_next_label'.
+
+The most complicated case is where you can offer multiple types of
+matches. In this case the user should be able to say which types he
+wants to see at all and of those which he wants to see he should be
+able to say which types should be tried first. The generic solution
+for this uses `_tags' and `_requested':
+
+  local expl ret=1
+
+  _tags friends users hosts
+
+  while _tags; do
+    _requested friends expl friend compad alice bob && ret=0
+    _requested users && _users && ret=0
+    _requested hosts && _hosts && ret=0
+
+    (( ret )) || break   # leave the loop if matches were added
+  done
+
+`_tags' with tags as arguments registers those tags and checks which
+of them the user wants to see and in which order the tags are to be
+tried. This means that internally these tags are stored in multiple
+sets. The types of matches represented by the tags from the first set
+should be tried first. If that generates no matches, the second set is
+tried and so on. `_tags' without arguments just makes the next set be
+tried (on the first call it makes the first set be used). The function
+`_requested' then tests if the tag given as its first argument is in
+the set currently used and returns zero if it is,  i.e. if matches of
+that type should be added now. The arguments accepted by `_requested'
+are the same as for `_wanted'. I.e. you can call it with only the tag
+to test, with the `tag array description' or with that plus the
+command to execute.
+
+In some cases (like the `users' and `hosts' tags in the example) you
+don't need do the loop over the tag aliases yourself, because the
+utility functions like `_users' and `_hosts' do it automatically.
+
+This looks good already. But in many cases such as this one you can
+also use the function `_alternative' which simply implements a loop
+like the one above. It gets arguments of the form `tag:descr:action'.
+E.g.:
+
+  _alternative \
+      'friends:friend:(alice bob)' \
+      'users:: _users' \
+      'hosts:: _hosts'
+
+Which does the same as the previous example. (Note the empty
+descriptions in the last two arguments -- the actions start with a
+space so that they are executed without giving the description
+build by `_alternative', i.e. we just use the description added by
+`_users' and `_hosts').
+
+In cases where you have to keep track of the context yourself, you can 
+give the sub-context you want to use to `_tags', `_wanted' and
+`_alternative' with the `-C' option as described above. You don't need 
+to give it to `_requested' -- that function will work on the context
+used by the corresponding call to `_tags' automatically.
+
+For the names of the tags: choose simple (short, if at all possible)
+names in plural. Also, first have a look at the tag names already used 
+by other functions and if any of these names seem sensible for the
+type of matches you are about to add, the use those names. This will
+allow users to define styles for certain types of matches independent
+of the place where they are added.
+
+One final comment about when to use your own argument-contexts: do
+this when the command you are writing a completion function for has
+different `modes'. E.g. if it accepts host names after a `-h' option
+and users or hosts after `-u' and for some reason you can't use
+`_arguments' to do the work for you, then use context names as in:
+
+  case "$1" in
+  -h)
+    _tags -C -h hosts && _hosts && ret=0
+    ;;
+  -u)
+    _alternative -C -u 'users:: _users' 'hosts:: _hosts' && ret=0
+    ;;
+  esac
+
+
+Styles
+------
+
+Users can associate patterns for hierarchical context names with
+certain styles using the `zstyle' builtin. The completion code
+should then use these styles to decide how matches should be added and 
+to get user-configured values. This, too,  is done using the builtin
+`zstyle'.
+
+Basically styles map names to a bunch of strings (the `value'). In
+many cases you want to treat the value as a boolean, so let's start
+with that. To test if, for example, the style `verbose' is set for 
+the tag `options' in the context you are currently in, you can just do:
+
+  if zstyle -t ":completion:${curcontext}:options" verbose; then
+    # yes, it is set...
+  fi
+
+I.e. with the -t option and two arguments `zstyle' takes the first one
+as a context and the second one as a style name and returns zero if that
+style has the boolean value `true'. Internally it checks if the style
+is set to one of `yes', `true', `on', or `1' and interprets that as
+`true' and every other value as `false'.
+
+For more complicated styles for which you want to test if the value
+matches a certain pattern, you can use `zstyle' with the -m option and
+three arguments:
+
+  if zstyle -m ":completion:${curcontext}:foo" bar '*baz*'; then
+    ...
+  fi
+
+This tests if the value of the style `bar' for the tag `foo' matches
+the pattern `*baz*' and returns zero if it does.
+
+If you just want to see if one of the strings in the value is exactly
+equal to any of a number of a strings, you can use the -t option and
+give the strings after the style name:
+
+  if zstyle -t ":completion:${curcontext}:foo" bar str1 str2; then
+    ...
+  fi
+
+But sometimes you want to actually get the value stored for a certain
+style instead of just testing it. For this `zstyle' supports four
+options: `-b', `-s', `-a', and `-h'. After these options, three
+arguments are expected, the context, the style, and a parameter name.
+The parameter will then be set to the value of the style and the option
+says how the strings stored as a value will be stored in the
+parameter:
+
+  - `-b': the parameter will be set to a either `yes' or `no'
+  - `-s': the parameter will be set to all strings in the value
+          concatenated (separated by spaces) to one string
+  - `-a': the parameter will be set to an array containing the strings 
+          from the value as elements
+  - `-h': the parameter will be set to an association with the strings 
+          from the value being interpreted alternatingly as keys and
+	  values
+
+Some random comments about style names. Use the ones already in use if 
+possible. Especially, use the `verbose' style if you can add
+matches in a simple and a verbose way. Use the verbose form only if
+the `verbose' style is `true' for the current context. Also, if
+the matches you want to add have a common prefix which is somehow
+special, use the `prefix-needed' and `prefix-hidden' styles. The first 
+one says if the user has to give the prefix on the line to make these
+matches be added and the second one says if the prefix should be
+visible in the list.
+
+And finally, if you need a style whose value can sensibly be
+interpreted as a list of words, use array or association styles with
+the `-a' or `-h' options to `zstyle'. Otherwise you should only make
+sure that an empty value for a style is treated in the same way as if
+the style wasn't set at all (this is used elsewhere and we want to
+keep things consistent).
+
+
+Descriptions
+------------
+
+Always use description. This is important. Really. *Always* use
+descriptions. If you have just written down a `compadd' without a
+"$expl[@]" (or equivalent), you have just made an error. Even in
+helper functions where you use a "$@": if you can't be sure that there 
+is a description in the arguments, add one. You can (and, in most
+cases, should) then give the description you generated after the
+"$@". This makes sure that the, probably more specific, description
+given by the calling function takes precedence over the generic one
+you have just generated.
+
+And it really isn't that complicated, is it? Think about a string
+people might want to see above the matches (in singular -- that's used 
+throughout the completion system) and do:
+
+  local expl
+
+  _description tag expl <descr>
+  compadd "$expl@]" - <matches ...>
+
+Note that this function also accepts `-V' und `-J', optionally (in the 
+same word) preceded by `1' or `2' to describe the type of group you
+want to use. For example:
+
+  _description tag expl '...'
+  compadd "$expl[@]" -1V foo - ...    # THIS IS WRONG!!!
+
+is *not* the right way to use a unsorted group. Instead do:
+
+  _description -1V tag expl '...'
+  compadd "$expl[@]" - ...
+
+and everything will work fine.
+
+Also, if you are about to add multiple different types of matches, use 
+multiple calls to `_description' and add them with multiple calls to
+`compadd'. But in almost all cases you should then add them using
+different tags anyway, so, see above.
+
+And since a tag directly corresponds to a group of matches, you'll
+often be using the tags function that allows you to give the
+explanation to the same function that is used to test if the tags are
+requested (again: see above). Just as a reminder:
+
+  _wanted [ -[1,2]V | -[1,2]J ] <tag> expl <descr>
+
+and
+
+  _requested [ -[1,2]V | -[1,2]J ] <tag> expl <descr>
+
+is all you need to make your function work correctly with both tags
+and description at the same time.
+
+
+Misc. remarks
+-------------
+
+1)  Supply match specifications to `compadd' if there are sensible ones.
+2)  Use helper functions that do option completion for you (like
+    `_arguments' and `_values') -- it will make your life much
     easier.
-7)  Use helper functions like `_users' and `_groups' instead of direct
-    calls to `compgen -u' or some ad hoc mechanisms to generate such
-    information. This ensures that user can change the way these things 
-    will be completed everywhere by just using their own implementations 
-    for these functions.
-8)  Make sure that the return value of your functions is correct: zero
+3)  Use helper functions like `_users' and `_groups' instead of some ad hoc
+    mechanisms to generate such information. This ensures that users can
+    change the way these things will be completed everywhere by just using
+    their own implementations for these functions.
+4)  Make sure that the return value of your functions is correct: zero
     if matches where added and non-zero if no matches were found.
     In some cases you'll need to test the value of `$compstate[nmatches]'
     for this. This should always be done by first saving the old value
     (`local nm="$compstate[nmatches]"') and later comparing this with
     the current value after all matches have been added (e.g. by
-    writing `[[ nmm -ne compstate[nmatches] ]]' at the end of your
-    function). This guarantees that your functions will be re-usable
-    because calling functions may rely on the correct return value.
-9)  In places where different behaviors may be useful, add a
-    configuration key to allow users to select the behavior they
-    prefer. Names for configuration keys should look like `prefix_name',
-    where `prefix' is the (probably abbreviated) name of your function
-    and `name' describes what can be configured.
-    When testing the values of configuration keys, the empty string
-    should result in the same behavior as if the key were unset. This
-    can be achieved by the test `[[ -n "$compconfig[prefix_name]" ]]'.
-10) When writing helper functions that generate matches, the arguments
-    of these should be given unchanged to `compadd' or `compgen' (if
-    they are not used by the helper function itself).
+    writing `[[ nm -ne compstate[nmatches] ]]' at the end of your
+    function).
+    This guarantees that your functions will be re-usable because calling
+    functions may rely on the correct return value.
+5)  When writing helper functions that generate matches, the arguments
+    of these should be given unchanged to `compadd' (if they are not
+    used by the helper function itself).
+6)  When matches with a common prefix such as option names are generated,
+    add them *with* the prefix (like `-', `+', or `--' for options).
+    Then check the `prefix-needed' style to see if the matches are to be
+    added when the prefix is on the line and use the `prefix-hidden'
+    style to see if the prefix should be listed or not.
+7)  If at all possible, completion code for a command or a suite of
+    commands should go into only one file. If a command has sub-commands,
+    implementing a state-machine might be a good idea. See the `_rpm' 
+    and `_pbm' files for examples of different styles. Also see the
+    documentation for `_arguments' and `_values' for two functions
+    that may help you with this.
+8)  If a completion function generates completely different types of
+    completions (for example, because the comamnd has several
+    completely different modes), it should allow users to define
+    functions that separately override the behavior for these
+    different types. This can easily be achieved by using the
+    `_funcall' utility function, as in:
+
+      _funcall ret _command_$subcommand && return ret
+
+    This will try to call the function `_command_$subcommand' and if
+    it exists, it will be called and the completion function exits
+    with its exit status. After this call to `funcall' the completion
+    function would contain the code for the default way to generate
+    the matches.
+    See the `_rpm' and `_nslookup' files for examples.