Repository: neovim
Issue: docs/nvim_cmd: semantics of `-range`, `-count` for user-defined commands
Labels: documentation,api

### Problem

There is currently a lack of clarity on whether commands that take both a range and a count are allowed, and if they are allowed, what their behavior is. I'll discuss each of these in turn.

## Ambiguity around what is allowed
The documentation for `:h command-range` begins as follows:
https://github.com/neovim/neovim/blob/3bf887f6e08fa272679187340ca483809275b20a/runtime/doc/map.txt#L1468-L1473
The language here suggests that there are three mutually-exclusive options: (1) `-range`/`-range=%`, (2) `-range=N`, and (3) `-count`/`-count=N`. This is intuitive - a command can take a range or a count, and if the latter, it may or may not be allowed to be passed in the argument position. This is how `nvim_create_user_command` interprets the rules -- if both `range` and `count` are defined in `opts`, the call fails.

However, there's another possible interpretation. The documentation goes on to say:
https://github.com/neovim/neovim/blob/3bf887f6e08fa272679187340ca483809275b20a/runtime/doc/map.txt#L1486-L1487
The language here suggests that while `-range=N` is mutually exclusive with all other options (explicitly noted here as mutually-exclusive with `-count`/`-count=N` and implicitly with the other `-range` options since they share the same attribute name), `-count`/`-count=N` and `-range`/`-range=%` can be specified at the same time, allowing a command to accept both a range and a count. This makes some sense when you think about the nature of the mutual exclusion -- `-range=N` vs. `-count=N` expresses whether or not you want to allow the count to be passed in the argument position, so saying both doesn't make sense, and if you only allow the count to be passed in the line number position, there's nowhere to specify the range. But in the case of `-count`/`-count=N` and `-range`/`-range=%`, you can pass the range in the line number position and the count in the argument position, so that could be supported. This is how `:command` interprets the rules - these combinations are allowed.

It is also worth noting here that `nvim_get_command`, which returns structured command definitions, and `nvim_parse_cmd`, which returns structured command invocations, both elide the distinction between commands that just take `-count` and the commands that take both `-count` and `-range` -- they return the same structures in each case, filling in both `range` and `count`.

## Ambiguity around behavior
So we've seen that if you use the native `:command` command instead of going via the API, it is possible to define commands that take both a range and a count. How should this work? There are four relevant escape sequences:
https://github.com/neovim/neovim/blob/3bf887f6e08fa272679187340ca483809275b20a/runtime/doc/map.txt#L1657-L1665
And the only documentation `<count>` could be referencing is the one mention of it we saw earlier:
https://github.com/neovim/neovim/blob/3bf887f6e08fa272679187340ca483809275b20a/runtime/doc/map.txt#L1470-L1473
The documentation does not define what should happen to `<count>` in commands that only take ranges, or to `<line1>`, `<line2>`, and `<range>` in commands that only take counts. We can leave it as undefined, but let's do a cursory examination just to get a sense of what behavior we might expect in the well-behaved cases before we dive into how they behave for commands that take both counts and ranges.

#### Behavior for `-range` / `-range=%` commands:
| Range | \<line1\> | \<line2\> | \<range\> | \<count\> |
| --- | --- | --- | --- | --- |
| | default | default | 0 | -1 |
| 2 | 2 | 2 | 1 | 2 |
| 2,5 | 2 | 5 | 2 | 5 |

So far so good.

#### Behavior for `-range=N` commands:
| Range | \<line1\> | \<line2\> | \<range\> | \<count\> |
| --- | --- | --- | --- | --- |
| | default for `-range` | default for `-range` | 0 | N |
| 2 | 2 | 2 | 1 | 2 |
| 2,5 | 2 | 5 | 2 | 5 |
| 3,6 | 3 | 6 | 2 | 6 |

Passing a 2-argument range is nonsense for a `-range=N` command, which just takes a count in the range position, but it's accepted. `<line1>`, `<line2>` and `<range>` are given sensible-enough values. Notably, `<line2>` cannot be used in lieu of `<count>`, as only `<count>` has the correct default (N) when no count is given.

#### Behavior for `-count` / `-count=N` commands:
In this table, "Range" means the value that was passed in the line number position, and "Arg1" means the value that was passed as the first argument.

| Range | Arg1 | \<line1\> | \<line2\> | \<range\> | \<count\> |
| --- | --- | --- | --- | --- | --- |
| | | default for `-range` | 1 | 0 | N |
| 2 | | 2 | 2 | 1 | 2 |
| | 2 | default for `-range` | 2 | 1 | 2 |
| 2 | 5 | 2 | 5 | 1 | 5 |
| 2,5 | | 2 | 5 | 2 | 5 |
| 3,6 | | 3 | 6 | 2 | 6 |
| 3,6 | 8 | 3 | 8 | 2 | 8 |

This is similar to the above table. `<line1>`, `<line2>` and `<range>` are given sensible-enough values.
And again, `<line2>` cannot be used in lieu of `<count>` b/c they differ in the default case, but this time they differ in a different way! Moreover, it is now possible for `<line1>` and `<line2>` to be a backwards range, which would normally abort a command if not repaired. Also notice that when a binary range and a count argument are passed, `<line2>` assumes the value of the count argument. Again, the takeaway here is that really only `<count>` should be used.

### Returning to the `-range`/`-range=%` & `-count`/`-count=N` case:
Now things get really confusing. You might expect that this case should actually be the most well-defined, because all of the undefined behavior before came from `<line2>` in commands that only take counts. If the command accepts a range *and* a count, the range values can be used for the range and the count argument could be used for the count. Alas, it is not that simple.

#### If the range attribute is specified before the count attribute in the command definition
| Range | Arg1 | \<line1\> | \<line2\> | \<range\> | \<count\> |
| --- | --- | --- | --- | --- | --- |
| | | default for the range attribute (`-range`/`-range=%`) | default for the range attribute (`-range`/`-range=%`) | 0 | N |
| 2 | | 2 | 2 | 1 | 2 |
| | 2 | default for the range attribute (`-range`/`-range=%`) | default for the range attribute (`-range`/`-range=%`) + 2 - 1 | 1 | default for the range attribute (`-range`/`-range=%`) + 2 - 1 |
| 2 | 5 | 2 | 6 | 2 | 6 |
| 2,5 | | 2 | 5 | 2 | 5 |
| 3,6 | | 3 | 6 | 2 | 6 |
| 3,6 | 8 | 6 | 13 | 3 | 13 |

It looks like maybe `<count> = count + end_of_range - 1`? And `<range>` is 3, even though it is only allowed to range between 0 and 2 in the manual.

#### If the count attribute is specified before the range attribute in the command definition
The semantics of `-count -range`/`-count=N -range` seem to be the same as the regular `-count`/`-count=N` semantics. The semantics of `-count -range=%`/`-count=N -range=%` seem *almost* the same, except the default case is different -- `<line1>` is 1 and `<line2>` is the default for `-range=%`.

### Recommendations

1. I think the most straightforward choice here is to give `:command` the semantics of `nvim_create_user_command` -- make it display an error when `-range`/`-range=%` and `-count`/`count=N` are both given. Then update `:h command-range` to explain that usage of any of the `-range` options is mutually-exclusive with any of the `-count` options.

2. I think it should be an error to pass a 2-argument range for `-range=N` and `-count`/`-count=N` commands.

3. If it's possible to examine the command definitions to see if `<line1>`, `<line2>`, or `<range>` is used without `-range`/`-range=%` and if `<count>` is used without `-range=N`/`-count`/`-count=N`, then make this an explicit error and fail to define the command. If this isn't possible, either always substitute a known dummy value (0? -1?) and document that behavior.

### Vim (not Nvim) behaves the same?

Probably yes for the parts of this that aren't related to the API. Haven't checked.