1

I've created a mapping to a function that accepts a range. Like this:

command! -range Test call TestFunc(<range>,<line1>,<line2>)
nnoremap <leader>t :Test<CR>

How do I supply a specific range to the mapped command, in normal mode?

For example: from current line to line 81, apply mapped command t. Something like .,81\t.

How can the same thing be done using marks?

The function, by the way, is written thus:

function! TestFunc(r,l1,l2) abort
    if a:r == 0
        echo "No range"
    elseif a:r == 1
        echo "Single Line given (like :22Test)"
    else
        echo "Line range given (like :1,23Test, '<,'>Test or %Test)"
    endif
endfunction

It simply says what sort of range it got. (I don't remember what the abort statement was in aid of).

When I have actually applied this to functions, I've required it to work both with and without ranges.

3
  • I would probably adjust the mapping to be :nnoremap <leader>t :Test<C-b>; then you can type the range and press enter.
    – D. Ben Knoble
    Dec 20, 2021 at 17:40
  • How would you type the range - you mean in command mode? Typing a range with a comma in command mode seems not to work.
    – markling
    Dec 20, 2021 at 19:23
  • Yes, e.g., \t.,81<cr>, since <C-b> puts your cursor between : and Test.
    – D. Ben Knoble
    Dec 20, 2021 at 21:29

3 Answers 3

4

Assuming the cursor is on line #80 and <leader> defaults to backslash, type directly 2\t.

The point is that typing N: in Normal mode, where N is arbitrary number, is converted by Vim to :.,.+N-1. So you have to supply number of lines in range (rather than last line number).

2
  • 1
    Yes, this is nice. I do that already. Problem is when you want to apply the command to a range of lines that goes off the screen. Like from 133 to 1781, for example. If I'm on line 133, surely there's a command mode way of saying, 'here's a line number - run the command on all lines between here and there?
    – markling
    Dec 20, 2021 at 19:25
  • @marking Well, you can modify your mapping to pass v:count as last line number instead. But you hardly want to have two mappings for the same operation. So it's more custom to do this in Visual like V1781gg\t and leave v:count for line range as it normally is.
    – Matt
    Dec 20, 2021 at 21:34
2

There are typically two ways to pass a range to a Normal-mode command.

One is to use a Visual selection (which is not exactly a Normal-mode command, but a Visual-mode one, but still pretty close) and the other is to create a mapping that takes an operator and acts on the range resulting of the operator or motions that follows it.

For the Visual-mode command, you get the '< and '> marks set to the beginning and end of the Visual selection, so you can simply call your user command with:

:'<,'>Test

But since Vim will add the range automatically when you press : from Visual mode (which already shows up with :'<,'>), you don't even need to add that into your mapping! All you need is:

xnoremap <leader>t :Test<CR>

To use this mapping on range from current line to line 81, you can use:

V81G\t

That's assuming backslash as your <leader> key. V will start Visual mode, 81G move to line 81, extending the selection and finally \t invoke the Visual-mode mapping.

For the second option, creating a mapping that expects an operator, you set the 'operatorfunc' option to a function that will act on the range, then issue the g@ command, which waits for an operator and invokes the function with the '[ and '] marks set.

In your case:

function! TestOpfunc(type)
    '[,']Test
endfunction
nnoremap <silent> <leader>t :set operatorfunc=TestOpfunc<CR>g@

You can then invoke it with:

\t81G

Where 81G is the motion taken as the operator to define the range where to act, in this case from the current line until line 81.

See :help :map-operator or :help g@ for more details and examples on how to use g@ and define an operator function. The examples also show how to reuse the function for the counterpart Visual-mode mapping.

3
  • I've been avoiding visual mode. But what you propose --- V81G\t --- would be ideal if I could get it to work! The other thing looks pretty groovy. But I shall have to find time to rewrite the function(s) where I'm actually doing this? One proviso is that the target functions must work also if they are not given a range.
    – markling
    Dec 20, 2021 at 19:32
  • When I do V81G\t, nothing happens! - it just swallows the \t and remains in --Visual Mode--, waiting....
    – markling
    Dec 20, 2021 at 19:36
  • That's odd about the mapping not working... If you use V81G for the Visual selection, then : (will show up with the range, as in the answer), then you run Test command in that range, will it work? About the Operator-Pending one. No, you won't need to rewrite anything, the example I gave above should work directly with your user-command, assuming it can take ranges just fine... You can refactor it to have a single function that does it all (depending on it's arguments), but you don't need to...
    – filbranden
    Dec 20, 2021 at 20:05
2

My preferred method here is to just let you enter the range from the place you normally do: on the Ex command line:

nnoremap <leader>t :Test<C-b>

Since <C-b> puts your cursor between at the beginning of the line, you can type \t.,81<CR>.

1
  • 1
    Appending ., to the end of mapping might save two keystrokes... Or sometimes result in typing two extra backspaces, of course.
    – Matt
    Dec 20, 2021 at 21:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.