How to get visually selected text in VimScript

后端 未结 11 1814
北荒
北荒 2020-11-27 14:30

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

相关标签:
11条回答
  • 2020-11-27 14:38

    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
    
    0 讨论(0)
  • 2020-11-27 14:39

    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.

    0 讨论(0)
  • 2020-11-27 14:44

    Overview:

    visually select, press mapping, reselect original selection with gv and copy it to a register, finally paste from the register

    Use case:

    1. Add function Test() to your vimrc:

    function! Test() range
    exe 'sp temp.tmp'
    exe 'norm p'
    endfunction

    1. Open a new file
    2. Create the mapping ,m
      :vmap ,m :norm gvy<Esc>:call Test()<CR>
    3. visually select some text
    4. press ,m (the selection is gone, but 'norm gv' reselects it and 'y' yanks it to current register)
    5. Test() is called: file temp.tmp is opened and 'norm p' pastes from current register, which is the original visual selection
    0 讨论(0)
  • 2020-11-27 14:48

    Added block selection to @xolox's great answer: mode() isnt used cause it this is planned to be used in function whereby the selection has been cleared by calling the operator etc.

    xnoremap <leader>a :<C-U> call GetVisualSelection(visualmode())<Cr>
    
    function! GetVisualSelection(mode)
        " call with visualmode() as the argument
        let [line_start, column_start] = getpos("'<")[1:2]
        let [line_end, column_end]     = getpos("'>")[1:2]
        let lines = getline(line_start, line_end)
        if a:mode ==# 'v'
            " Must trim the end before the start, the beginning will shift left.
            let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
            let lines[0] = lines[0][column_start - 1:]
        elseif  a:mode ==# 'V'
            " Line mode no need to trim start or end
        elseif  a:mode == "\<c-v>"
            " Block mode, trim every line
            let new_lines = []
            let i = 0
            for line in lines
                let lines[i] = line[column_start - 1: column_end - (&selection == 'inclusive' ? 1 : 2)]
                let i = i + 1
            endfor
        else
            return ''
        endif
        for line in lines
            echom line
        endfor
        return join(lines, "\n")
    endfunction
    
    0 讨论(0)
  • 2020-11-27 14:52

    I once wrote a function that is able to do it without touching registers or cursor position:

    function s:F.map.getvrange(start, end)
        let [sline, scol]=a:start
        let [eline, ecol]=a:end
        let text=[]
        let ellcol=col([eline, '$'])
        let slinestr=getline(sline)
        if sline==eline
            if ecol>=ellcol
                call extend(text, [slinestr[(scol-1):], ""])
            else
                call add(text, slinestr[(scol-1):(ecol-1)])
            endif
        else
            call add(text, slinestr[(scol-1):])
            let elinestr=getline(eline)
            if (eline-sline)>1
                call extend(text, getline(sline+1, eline-1))
            endif
            if ecol<ellcol
                call add(text, elinestr[:(ecol-1)])
            else
                call extend(text, [elinestr, ""])
            endif
        endif
        return text
    endfunction
    

    It is called like this:

    let [sline, scol, soff]=getpos("'<")[1:]
    let [eline, ecol, eoff]=getpos("'>")[1:]
    if sline>eline || (sline==eline && scol>ecol)
        let [sline, scol, eline, ecol]=[eline, ecol, sline, scol]
    endif
    let lchar=len(matchstr(getline(eline), '\%'.ecol.'c.'))
    if lchar>1
        let ecol+=lchar-1
    endif
    let text=s:F.map.getvrange([sline, scol], [eline, ecol])
    

    Note that at this point you will have a list of strings in text: one reason why I wrote this function is ability to keep NULLs in file. If you stick with any solution that yanks text in a register all NULLs will be replaced with newlines and all newlines will be represented as newlines as well. In the output of getvrange function though NULLs are represented as newlines while newlines are represented by different items: there is an NL between each list item, just like in output of getline(start, end).

    This function can only be used to get lines for characterwise selection (as for linewise it is much simpler and for blockwise I iterate over lines and do not need such function. There are also functions for deleting given range (without touching registers) and inserting text at given position (without touching registers or cursor).

    0 讨论(0)
  • 2020-11-27 14:53

    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.

    0 讨论(0)
提交回复
热议问题