about summary refs log tree commit diff
path: root/Etc/completion-style-guide
blob: f5ec8ff14f0b11baf530e04329f38ebd58e79453 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
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 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.

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. The functions `_arguments' and
`_values' do that too, but there is a small difference. These
functions effectively add a new component to the hierarchical context
name and if you are using the `->state' form for actions, this new
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 append the context name given to the `curcontext' parameter
(preceding it with a colon) 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 ... '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 `_tags' 
or the function `_wanted' for this. `_tags' is normally used to offer
multiple types of matche by giving the tags for them as arguments. But 
it any case its return value is zero only if at least one of these
types is requested by the user, so you can just do:

  _tags names || return 1

  _description expl 'name'
  compadd "$expl[@]" - alice bob

Since this sequence of command is used so often, the `_wanted'
function was added which just calls `_tags' with its first argument
(i.e. the first argument os a tag) and then calls `_description' with
all other arguments. The return value is as for `_tags' -- zero if the 
matches should be added. So the example becomes:

  _wanted names expl 'name' && compadd "$expl[@]" alice bob

Note that you can also give the `-J' and `-V' options with the
optional `1' or `2' following them supported by `_description':

  _wanted names -V2 expl 'name' && compadd ...

The more 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
    if _requested friends; then
      _description expl friend
      compad "$expl[@]" alice bob && ret=0
    fi
    _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 calls
`_sort_tags' so that the user can say which 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.

But `_requested' can do more: since it is very common that you add
different types of matches in different groups, with each group having 
its own description the sequence of `_requested' followed by
`_description' would be used very often. Hence, `_requested' can
accept extra arguments which will be given to a call to `_description' 
if the tag given as the first argument is to be used. I.e. we could
change the example above to:

  local expl ret=1

  _tags friends users hosts

  while _tags; do
    _requested friends expl friend && compad "$expl[@]" alice bob && ret=0
    _requested users && _users && ret=0
    _requested hosts && _hosts && ret=0

    (( ret )) || break   # leave the loop if matches were added
  done

This looks better already. But in many cases such as this one you can
also use the function `_laternative' which simply implements a loop
like this one. 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 examples. (Note the empty
descriptions in the last two arguments -- the actions start with a
space so that they are executed without giving the 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 indepent of
the place where they are added.

One final comment about when to use your own sub-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 `compstyle' function. The completion code
should then use these styles to decide how matches should be added and 
to get user-configured values. This is done using the function
`_style'.

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 `description' is set for 
the tag `options' in the context you are currently in, you can just do:

  if _style options description; then
    # yes, it is set...
  fi

I.e. with two arguments `_style' takes the first one as a tag 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 style for which you want to test if the value
matches a certain pattern, you can use `_style' with three arguments:

  if _style 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.

But sometimes you want to actually get the value stored for a certain
style instead of just testing it. For this `_style' supports four
options: `-b', `-s', `-a', and `-h'. After these options, three
arguments are expected, namely the tag, 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

Note that if you want to test or get styles for a certain context
name which you have to build yourself, you have to call `_style' after 
the call to `_tags', `_wanted', or whatever. When you are using
utility functions like `_alternate' or `_arguments' the context will
automatically be set up appropriately at the time when you have a
chance to call `_style'.

Some random comments about style names. Use the ones already in use if 
possible. Especially, use the `description' style if you can add
matches in a simple and a verbose way. Use the verbose form only if
the `description' 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.

But, I think, using any number of new style names is ok -- different
from tag-names where I would like to keep the number of names used
small.

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 `_style'. 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 use 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 expl <descr>
  compadd "$expl@]" - <matches ...>

Note that this function also accepts `-V' und `-J', optionally (in the 
same word) followed by `1' or `2' to describe the type of group you
want to use. For example:

  _description expl '...'
  compadd "$expl[@]" -V1 foo - ...    # THIS IS WRONG!!!

is *not* the right way to use a unsorted group. Instead do the
simpler:

  _description -V1 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 often a tag directly corresponds to a group of matches,
you'll often be using the tags function that allow 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 <tag> [ -V[1,2] | -J[1,2] ] expl <descr>

and

  _requested <tag> [ -V[1,2] | -J[1,2] ] 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.
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 `[[ 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' or `compgen' (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.