如何在VimScript中获取视觉选择的文本

时间:2009-10-07 19:09:36

标签: vim

我可以使用getpos()获取光标位置,但我想在一行中检索所选文本,即'<,'>。这是怎么做到的?

更新

我想我编辑了我解释过的部分,我希望从Vim脚本中获取 ...

11 个答案:

答案 0 :(得分:71)

我来到这里询问与主题启动器相同的问题并尝试了Luc Hermitte的代码,但它对我不起作用(当我的代码执行时视觉选择仍然有效)所以我写了下面的函数,这似乎工作正常:

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

我希望这对某人有用!

更新(2013年5月):实际上这还不太正确,我最近在我发布的一个Vim插件中修复了以下错误:

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

更新(2014年5月):此(普通)代码特此授权为公共域。做它你想要的。学分很受欢迎,但不是必需的。

答案 1 :(得分:16)

我找到的最好的方法是将选择粘贴到寄存器中:

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

答案 2 :(得分:13)

在Linux上,有一种廉价但有效的替代方法可以自己编写这样的GetVisualSelection()函数:使用*寄存器!

*寄存器包含最新Visual选择的内容。请参阅:h x11-selection

在您的脚本中,您只需访问@*即可获得视觉选择。

let v = @*

顺便说一下,*也是交互式使用中的一个整洁的小帮手。例如,在插入模式下,您可以使用CTRL-R *插入之前选择的内容。没有明确的贬义。

这仅适用于支持X11选择机制的操作系统。

答案 3 :(得分:7)

我不完全确定这里的上下文,因为getpos()确实可以接受标记(如'<'>)作为参数。

但是,为了对你可能要求的内容进行抨击,还有v,它类似于'<,除了它始终更新(即用户仍处于可视模式时)。这可以与当前光标位置.结合使用,然后表示视觉选择的结束。

编辑:我在:help line()找到了这些内容。包括line()getpos()在内的几个函数具有相同的可能参数集。

编辑:我猜你可能只是简单地询问如何在两个任意标记之间获取文本,而不是逐行...(即,这并不特别适用于可视模式)。我认为实际上没有办法。是的,这似乎是一个非常明显的遗漏。您应该能够通过找到getpos()的标记来伪造它,使用getline()获取所有行,然后根据列位置切断第一个和最后一个(使用casework取决于是否它是多线的)。对不起,这不是一个真正的答案,但至少你可以将它包装在一个函数中而忘记它。

答案 4 :(得分:5)

我曾经写过一个能够在不触及寄存器或光标位置的情况下完成的功能:

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

它被称为:

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])

请注意,此时您将在文本中包含一个字符串列表:我编写此函数的一个原因是能够在文件中保留NULL。如果您坚持使用任何在文本寄存器中移动文本的解决方案,则所有NULL将替换为换行符,并且所有换行符也将表示为换行符。在getvrange函数的输出中虽然NULL表示为换行符,而换行符由不同的项表示:每个列表项之间都有一个NL,就像在getline(start, end)的输出中一样。

此函数只能用于获取用于字符选择的行(对于行,它更简单,并且对于逐行迭代我不需要这样的函数。还有用于删除给定范围的函数(不接触寄存器)并在给定位置插入文本(不接触寄存器或光标)。

答案 5 :(得分:3)

这是一个相当古老的问题,但是因为我可以想象很多人会在某些时候遇到它,这是我修改过的@xolox答案

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. '<'>不会得到更新,因此在这种情况下需要使用.v
  2. 可以在可视模式下向后选择文本,这意味着'>在文本中位于'<之前。在这些情况下,这两个职位只需要颠倒过来。
  3. 虽然这不在我的函数版本中,但是如果选择是向后的,则可以选择反转字符串。这是一段剪辑,展示了如何做到这一点。
  4. 假设当标记的顺序相反时定义变量“reverse”:

    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
    

答案 6 :(得分:3)

为@xolox的好答案添加了块选择: mode()未使用是因为它计划用于通过调用操作符等清除选择的功能。

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

答案 7 :(得分:1)

我认为你应该使用“clipboard resgisiter”。

有关详细信息,您可以阅读帮助':h clipboard-autoselect'

如果启用此选项(设置clipboard = unnamed,autoselected),则 你可以像这样更容易地获得选定的文本 “ 让l:text = @ * “

答案 8 :(得分:1)

vim-asterisk获取的此函数也适用于<expr>映射,支持逐块选择和多字节列。

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

答案 9 :(得分:0)

概述:

直观选择,按映射,用gv重新选择原始选择并将其复制到寄存器,最后从寄存器粘贴

使用案例

  1. 将函数Test()添加到vimrc:
  2.   

    功能!测试()范围
      exe'sp temp.tmp'
      exe'norm p'
      endfunction可写

    1. 打开新文件
    2. 创建映射,m
      :vmap,m:norm gvy&lt; Esc&gt;:调用Test()&lt; CR&gt;
    3. 直观地选择一些文字
    4. 按,m(选择消失,但'norm gv'重新选择它,'y'将它拉到当前注册表中)
    5. 调用Test():文件temp.tmp打开,'norm p'从当前寄存器粘贴,这是原始的视觉选择

答案 10 :(得分:0)

@DarkWiiPlayer答案的修改版本。

区别是:

1。 它会测试&selection在何时提供正确的功能

:behave mswin

以及默认设置:

:behave xterm

2。 它还适用于可视块选择测试visualmode()

我还将视觉选择返回为线阵(因为这是我需要的)。但是,如果您需要一个信号文本块,则将return join(lines, "\n")留在注释中。

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