How to get visually selected text in VimScript

I'm able to get the cursor position with getpos(), but I want to retrieve the selected text within a line, that is '<,'>. How's this done?

UPDATE

I think I edited out the part where I explained that I want to get this text from a Vim script...


I came here asking the same question as the topic starter and tried the code by Luc Hermitte but it didn't work for me (when the visual selection is still in effect while my code is executed) so I wrote the function below, which seems to work okay:

function! s:get_visual_selection()
    let [line_start, column_start] = getpos("'<")[1:2]
    let [line_end, column_end] = getpos("'>")[1:2]
    let lines = getline(line_start, line_end)
    if len(lines) == 0
        return ''
    endif
    let lines[-1] = lines[-1][: column_end - 2]
    let lines[0] = lines[0][column_start - 1:]
    return join(lines, "\n")
endfunction

I hope this is useful to someone!

Update (May 2013): Actually that's not quite correct yet, I recently fixed the following bug in one of the Vim plug-ins I published:

function! s:get_visual_selection()
    " Why is this not a built-in Vim script function?!
    let [line_start, column_start] = getpos("'<")[1:2]
    let [line_end, column_end] = getpos("'>")[1:2]
    let lines = getline(line_start, line_end)
    if len(lines) == 0
        return ''
    endif
    let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
    let lines[0] = lines[0][column_start - 1:]
    return join(lines, "\n")
endfunction

Update (May 2014): This (trivial) code is hereby licensed as public domain. Do with it what you want. Credits are appreciated but not required.


On Linux, there is a cheap but effective alternative to programming such a GetVisualSelection() function yourself: use the * register!

The * register contains the content of the most recent Visual selection. See :h x11-selection.

In your script you could then simply access @* to get the Visual selection.

let v = @*

Incidentally, * is also a neat little helper in interactive use. For example, in insert mode you can use CTRL-R * to insert what you had selected earlier. No explicit yanking involved.

This works only on operating systems that support the X11 selection mechanism.


The best way I found was to paste the selection into a register:

function! lh#visual#selection()
  try
    let a_save = @a
    normal! gv"ay
    return @a
  finally
    let @a = a_save
  endtry
endfunction

This is quite an old question, but since I can imagine lots of people will come across it at some point, here's my modified version of @xolox answer

function! VisualSelection()
    if mode()=="v"
        let [line_start, column_start] = getpos("v")[1:2]
        let [line_end, column_end] = getpos(".")[1:2]
    else
        let [line_start, column_start] = getpos("'<")[1:2]
        let [line_end, column_end] = getpos("'>")[1:2]
    end
    if (line2byte(line_start)+column_start) > (line2byte(line_end)+column_end)
        let [line_start, column_start, line_end, column_end] =
        \   [line_end, column_end, line_start, column_start]
    end
    let lines = getline(line_start, line_end)
    if len(lines) == 0
            return ''
    endif
    let lines[-1] = lines[-1][: column_end - 1]
    let lines[0] = lines[0][column_start - 1:]
    return join(lines, "\n")
endfunction
  1. '< and '> don't get updated when the user is still in visual mode, thus . and v need to be used in that case.
  2. It is possible to select text backwards in visual mode, which means '> comes before '< in the text. In those cases the two positions simply need to be reversed.
  3. While this isn't in my version of the function, one could choose to reverse the string if the selection was backwards. Here's a snipped that shows how to do this.

Assuming the variable "reverse" is defined when the marks are in reverse order:

if exists("reverse")
    let lines_r = []
    for line in lines
        call insert(lines_r, join(reverse(split(line, ".\\zs"))))
    endfor
    return join(lines_r, "\n")
else
    return join(lines, "\n")
end

I'm not totally sure about the context here, because getpos() can indeed accept marks (like '< and '>) as arguments.

However, to take a stab at what you might be asking for, there's also v, which is like '< except it's always updated (i.e. while the user is still in visual mode). This can be used in combination with ., the current cursor position, which will then represent the end of the visual selection.

Edit: I found these in :help line(); several functions including line() and getpos() have the same set of possible arguments.

Edit: I guess you're probably simply asking how to get the text between two arbitrary marks, not going line-by-line... (i.e. this doesn't specifically pertain to visual mode). I don't think there actually is a way. Yes, this seems like a pretty glaring omission. You should be able to fake it by finding the marks with getpos(), getting all the lines with getline(), then chopping off on the first and last according to the column position (with casework depending on whether or not it's multi-line). Sorry it's not a real answer, but at least you can wrap it up in a function and forget about it.