Rich
Rich

Reputation: 303

Vim: How to detect type of range within a command?

Given this...

function s:MyFn()
endfunction

command -range MyCommand :call s:MyFn()

...how can I tell the difference between the type of range specified to a command (or a function that it calls)? Specifically, how do I tell the difference between a range specified as a pair of line numbers, and a range specified as a pair of marks (usually '<'>). In all my attempts and things I have read, this information seems to get lots as soon as the command is called.

I checked out the following, which sort-of get close, but nether seems to get to the nub of this problem;-

How to detect existence of Visual selection in VimL script

https://vi.stackexchange.com/questions/11025/

I find that if I do this...

function s:MyFn(range)
  if a:range == 0
    " No range specified
  elseif a:range == 1
    " Single line specified
  else
    " a:range == 2
  endif
endfunction

command -range MyCommand :call s:MyFn(<range>)

This at least solves part of the problem. But in the case of a:range == 2, I can't find a way of determining whether the range is a pair of line numbers or a pair of marks (usually, but not necessarily, from a visual selection). If the visual selection was line-wise then the distinction is largely academic. But the distinction matters for character-wise and block-wise visual selections. If I KNOW that a visual selection is in effect then I can work out the type of selection it is. The root problem is detecting whether a visual selection was in effect at all; I cannot just assume that a visual selection was used and use the '< and '> markers because the range could have been specified as two line numbers and the visual selection markers could be just left-over from a previous (unrelated) use

Upvotes: 2

Views: 525

Answers (2)

romainl
romainl

Reputation: 196751

The range is expanded to a pair of line numbers before being consumed by a command or function so the original range is effectively lost by the time you consume its expanded value.

:help getcmdline() can be used to… get the raw command line, range included, but it can only be used in a command-line mode mapping so you will have to be a bit creative. Here is a quick and dirty snippet to get you started:

" save the current range in a global variable
" insert the name of your custom command for further typing
function! FirstStep()
    let g:my_range = getcmdline()
    return 'MyCommand'
endfunction

" the actual implementation of the desired functionality
" do what you have to do with g:my_range
function! SecondStep()
    if g:my_range == "'<,'>"
        echo "range is purely visual"
    elseif g:my_range =~ '\d\+,\d\+'
        echo "range is purely numerical"
    else
        echo "range is neither purely visual nor purely numerical"
        echo "support for other ranges not yet implemented"
    endif
endfunction

" expression mapping
" inserts the result of FirstStep() in the command-line
cnoremap <expr> MyCommand FirstStep()

" the actual command that calls the actual function
" that actually implements the desired functionality
command! -range=% MyCommand call SecondStep()

It looks like this:

example

Upvotes: 3

Matt
Matt

Reputation: 15186

After you've pressed : (by key or by mapping) you already got into Command-line mode effectively abandoning any previous (be it Visual or Normal) mode. And so there's no way for your command / function to tell the difference between "get here from Visual or from other mode".

In fact, you have two options: either make a command always to use last visual mode (silently ignoring any range passed on command line); or start from a Visual mode mapping (maybe with "expr" or "cmd" etc.).

Upvotes: 1

Related Questions