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

gui*_*ooo 45 vim

我能够获得光标位置getpos(),但我想一行中检索所选文本,即'<,'>.怎么做的?

UPDATE

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

xol*_*lox 78

我来到这里询问与主题启动器相同的问题并尝试了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
Run Code Online (Sandbox Code Playgroud)

我希望这对某人有用!

更新(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
Run Code Online (Sandbox Code Playgroud)

更新(2014年5月):此(普通)代码特此许可为公共域.做它你想要的.积分表示赞赏但不是必需的.

  • @IdanArye:我根本不介意,想做你想做的事:-).我在原帖中添加了说明. (2认同)
  • @xolox 谢谢!我通过链接到您的答案来给予信任。https://github.com/someboddy/vim-vebugger/blob/master/autoload/vebugger/util.vim (2认同)
  • 问题:在其他地方已经提到过`'<`和`'>`直到选择结束后才更新.这打击了吗?应该是`v`和`.`而不是? (2认同)

Luc*_*tte 16

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

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


glt*_*lts 15

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

*寄存器包含最新Visual选择的内容.见:h x11-selection.

在您的脚本中,您可以简单地访问@*以获取Visual选择.

let v = @*
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这只适用于我认为使用+ x11选项编译vim的情况? (4认同)
  • 是的,我在 Ubuntu 上收到“E354:无效的寄存器名称:'*”。 (2认同)
  • 如果你的 vim 没有 `@*` x11-selection 功能,那么做同样事情的另一种选择是执行 `normal "ay` 将视觉选择填充到寄存器 `a` 中,然后执行 `let Visuals_selected_text = getreg('s', 1, 1)` 请参阅:https://vi.stackexchange.com/questions/12004/write-selected-area-to-file (2认同)

Dar*_*yer 14

这是一个很老的问题,但因为我可以想象很多人会在某个时候遇到它,这是我修改后的@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
Run Code Online (Sandbox Code Playgroud)
  1. '<'>做,当用户仍处于可视化模式,因此没有得到更新.,并v需要在这种情况下使用。
  2. 可以在视觉模式下向后选择文本,这意味着'>出现'<在文本之前。在这些情况下,这两个位置只需要颠倒过来。
  3. 虽然这不在我的函数版本中,但如果选择向后,则可以选择反转字符串。这是一个片段,展示了如何做到这一点。

假设当标记处于相反顺序时定义了变量“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
Run Code Online (Sandbox Code Playgroud)


Cas*_*bel 7

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

然而,为了抨击你可能要求的东西,还有v,'<除了它总是更新(即用户仍然处于可视模式)时.这可以与.当前光标位置结合使用,然后表示视觉选择的结束.

编辑:我发现了这些:help line(); 几个函数包括line()getpos()具有相同的可能参数集.

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


ZyX*_*ZyX 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
Run Code Online (Sandbox Code Playgroud)

它被称为这样:

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])
Run Code Online (Sandbox Code Playgroud)

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

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


run*_*ace 5

在 @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
Run Code Online (Sandbox Code Playgroud)

  • 您应该使用 `xnoremap` 而不是 `vnoremap` 进行可视化模式映射,请参阅[为什么 Visual * 和 * Select 模式都使用 `vmap`?](https://vi.stackexchange.com/questions/24895/why -is-vmap-for-both-visual-and-select-mode)了解详细信息。 (3认同)
  • 我更喜欢不使用选择模式的一致性,但出于其他原因,我更改了它。 (2认同)