How to get visually selected text in VimScript

后端 未结 11 1813
北荒
北荒 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:53

    I think you should use "clipboard resgisiter".

    For more detail, you might read help ':h clipboard-autoselect'

    If you enable this option( set clipboard=unnamed,autoselected),
    you ca get selected text more easily like this " let l:text = @* "

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

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

    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.

    0 讨论(0)
  • 2020-11-27 15:02

    This function taken from vim-asterisk also works in <expr> mappings, supports block-wise selections and multibyte columns.

    function! GetVisualSelection()
        let mode = mode()
        let end_col = s:curswant() is s:INT.MAX ? s:INT.MAX : s:get_col_in_visual('.')
        let current_pos = [line('.'), end_col]
        let other_end_pos = [line('v'), s:get_col_in_visual('v')]
        let [begin, end] = s:sort_pos([current_pos, other_end_pos])
        if s:is_exclusive() && begin[1] !=# end[1]
            " Decrement column number for :set selection=exclusive
            let end[1] -= 1
        endif
        if mode !=# 'V' && begin ==# end
            let lines = [s:get_pos_char(begin)]
        elseif mode ==# "\<C-v>"
            let [min_c, max_c] = s:sort_num([begin[1], end[1]])
            let lines = map(range(begin[0], end[0]), '
            \   getline(v:val)[min_c - 1 : max_c - 1]
            \ ')
        elseif mode ==# 'V'
            let lines = getline(begin[0], end[0])
        else
            if begin[0] ==# end[0]
                let lines = [getline(begin[0])[begin[1]-1 : end[1]-1]]
            else
                let lines = [getline(begin[0])[begin[1]-1 :]]
                \         + (end[0] - begin[0] < 2 ? [] : getline(begin[0]+1, end[0]-1))
                \         + [getline(end[0])[: end[1]-1]]
            endif
        endif
        return join(lines, "\n") . (mode ==# 'V' ? "\n" : '')
    endfunction
    
    let s:INT = { 'MAX': 2147483647 }
    
    " @return Number: return multibyte aware column number in Visual mode to select
    function! s:get_col_in_visual(pos) abort
        let [pos, other] = [a:pos, a:pos is# '.' ? 'v' : '.']
        let c = col(pos)
        let d = s:compare_pos(s:getcoord(pos), s:getcoord(other)) > 0
        \   ? len(s:get_pos_char([line(pos), c - (s:is_exclusive() ? 1 : 0)])) - 1
        \   : 0
        return c + d
    endfunction
    
    function! s:get_multi_col(pos) abort
        let c = col(a:pos)
        return c + len(s:get_pos_char([line(a:pos), c])) - 1
    endfunction
    
    " Helper:
    
    function! s:is_visual(mode) abort
        return a:mode =~# "[vV\<C-v>]"
    endfunction
    
    " @return Boolean
    function! s:is_exclusive() abort
        return &selection is# 'exclusive'
    endfunction
    
    function! s:curswant() abort
        return winsaveview().curswant
    endfunction
    
    " @return coordinate: [Number, Number]
    function! s:getcoord(expr) abort
        return getpos(a:expr)[1:2]
    endfunction
    
    "" Return character at given position with multibyte handling
    " @arg [Number, Number] as coordinate or expression for position :h line()
    " @return String
    function! s:get_pos_char(...) abort
        let pos = get(a:, 1, '.')
        let [line, col] = type(pos) is# type('') ? s:getcoord(pos) : pos
        return matchstr(getline(line), '.', col - 1)
    endfunction
    
    " @return int index of cursor in cword
    function! s:get_pos_in_cword(cword, ...) abort
        return (s:is_visual(get(a:, 1, mode(1))) || s:get_pos_char() !~# '\k') ? 0
        \   : s:count_char(searchpos(a:cword, 'bcn')[1], s:get_multi_col('.'))
    endfunction
    
    " multibyte aware
    function! s:count_char(from, to) abort
        let chars = getline('.')[a:from-1:a:to-1]
        return len(split(chars, '\zs')) - 1
    endfunction
    
    " 7.4.341
    " http://ftp.vim.org/vim/patches/7.4/7.4.341
    if v:version > 704 || v:version == 704 && has('patch341')
        function! s:sort_num(xs) abort
            return sort(a:xs, 'n')
        endfunction
    else
        function! s:_sort_num_func(x, y) abort
            return a:x - a:y
        endfunction
        function! s:sort_num(xs) abort
            return sort(a:xs, 's:_sort_num_func')
        endfunction
    endif
    
    function! s:sort_pos(pos_list) abort
        " pos_list: [ [x1, y1], [x2, y2] ]
        return sort(a:pos_list, 's:compare_pos')
    endfunction
    
    function! s:compare_pos(x, y) abort
        return max([-1, min([1,(a:x[0] == a:y[0]) ? a:x[1] - a:y[1] : a:x[0] - a:y[0]])])
    endfunction
    
    0 讨论(0)
  • 2020-11-27 15:03

    Modified version of @DarkWiiPlayer answer.

    The differences are:

    1. It test &selection to give the proper functionality when

    :behave mswin
    

    As well as the default:

    :behave xterm
    

    2. It also works properly for visual block selections testing visualmode()

    I'm also returned the visual selection as an array of lines (becuase that is what I needed). However I've left the return join(lines, "\n") in commented out if you need a signal text block instead.

    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
        if &selection ==# "exclusive"
            let column_end -= 1 "Needed to remove the last character to make it match the visual selction
        endif
        if visualmode() ==# "\<C-V>"
            for idx in range(len(lines))
                let lines[idx] = lines[idx][: column_end - 1]
                let lines[idx] = lines[idx][column_start - 1:]
            endfor
        else
            let lines[-1] = lines[-1][: column_end - 1]
            let lines[ 0] = lines[ 0][column_start - 1:]
        endif
        return lines  "use this return if you want an array of text lines
        "return join(lines, "\n") "use this return instead if you need a text block
    endfunction
    
    0 讨论(0)
提交回复
热议问题