It turns out that, when called without arguments, go()
actually sets 'operatorfunc'
and returns 'g@'
. (See this code snippet.)
So the concatenation of 'g@' . '_'
results in the g@_
command being executed in normal mode. The <expr>
in the mapping makes Vim evaluate it as an expression and finally execute the string result as a normal mode command (similar to how :execute normal!
would do, in this particular case.) See :help :map-<expr>
for more details.
The g@
command will wait for a motion and then apply the 'operatorfunc'
to that motion. See :help g@
. So you could use, for example, g@}
to apply it to the block going from the current line until the next blank line, or g@ip
to apply it to the paragraph under the cursor.
The _
motion moves to the beginning of the current line, so in that sense it's somewhat similar to the ^
motion, but it has two very important differences from the latter.
First, it's a linewise motion, so even though it will stay on the same line, when used as a motion for an operator, it will apply to the whole line and not just the characters from the cursor position to the beginning of the line. (For illustration, place the cursor in the middle of a line and see the difference between d^
and d_
.)
Second, when used with a count, it will move to the beginning of the [count] - 1
line below the current one. So 5_
will move down 4 lines and go to the beginning of that line (similar to 4j^
or 4+
.) But that's very useful in a motion for an operator, since when you include the current line up to 4 lines down, you end up with 5 lines, so 5_
is actually selecting a block of 5 lines, starting with the current line. (For illustration, see how 5d_
will delete 5 lines. That's equivalent to d5_
, the count may precede the operator-pending command as well.)
In effect, many Vi shortcuts doubling a letter are in fact equivalent to using _
. For example, dd
is the same as d_
and yy
is the same as y_
.
See :help _
. (Though, it's really brief, won't give you all these details and really draw attention to the two important details I highlighted here.)
So, in short, g@_
here will apply the 'operatorfunc'
to the current line. It will work on a line, since _
is a linewise motion. It will also take a count and act on a block of [count]
lines, due to the way _
behaves with a count. That's it!
We haven't really covered the 'operatorfunc'
being used here. (It's a bit out of scope of your question, but hopefully still on topic.) It turns out the go()
function is using a slightly convoluted way to set 'operatorfunc'
to itself. (See :help <sfile>
to see how you get to the function name.)
When g@
calls an 'operatorfunc'
it passes it a single argument, which indicates whether the motion was linewise, characterwise or blockwise (see :help g@
again for details.) So in case of g@_
, it will call go("line")
. Being called with a single argument will arrive at this codepath, which will make it act on the lines going from '[
to ']
, which are the marks set by g@
.
And, as advertised in the help of the commentary plug-in, the gcc
(comment line) binding will work with a count.