summaryrefslogtreecommitdiff
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.