diff options
Diffstat (limited to 'Etc/completion-style-guide')
-rw-r--r-- | Etc/completion-style-guide | 426 |
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. |