11

这是我的字符串:

"this is my sentence"

我想要这个输出:

"sentence my is this"

我想在一行中(在缓冲区中)选择几个单词并逐字反转。

谁能帮我?

4

4 回答 4

16

这里的上下文是什么并不完全清楚:您可能正在谈论缓冲区中一行中的文本或存储在 VimScript 变量中的字符串。

注意:对问题的不同解释导致了不同的方法和解决方案。
有一些“旧更新”大约在中途开始,已被该部分上方提到的插件或多或少地过时。我把它们留在里面是因为它们可能会为某些人提供有用的信息。

全线替换

因此,要将当前行中的文本存储在 vimscript 变量中的当前缓冲区中,您可以

let words = getline('.')

然后要颠倒他们的顺序,你只需要做

let words = join(reverse(split(words)))

如果你想用反转的词替换当前行,你做

call setline('.', words)

你可以用一条有点难以理解的线来做到这一点

call setline('.', join(reverse(split(getline('.')))))

甚至定义一个执行此操作的命令

command! ReverseLine call setline('.', join(reverse(split(getline('.')))))

部分行(按字符)选择

正如“旧更新”部分所述,在字符或块视觉选择上运行通用命令(前者是 OP 想要在此处执行的操作)可能非常复杂。:substitute即使使用按字符的视觉选择(如以 unshifted 启动)仅选择了部分行,Ex 命令也将在整行上运行v

在 OP 在下面评论之后,我意识到在部分行字符选择中反转单词可以很容易地完成

:s/\%V.*\%V./\=join(reverse(split(submatch(0))))/

其中

  • \%VRE 内匹配视觉选择的某些部分。显然,这不会在选择中的最后一个字符之后延伸:省略最后.一个字符将排除最后一个选定的字符。
  • \=替换的开头表示要作为 vimscript 表达式求值,但有一些区别
  • submatch(0)返回整个匹配。这有点像 perl 的$&,$1等,除了它仅在评估替换时可用。我认为这意味着它只能用于:substitute命令或调用substitute()

因此,如果您想对单行选择进行替换,这将非常有效。您甚至可以使用...\=system(submatch(0)).

多行字符选择

这似乎也适用于多行字符选择,但您必须小心删除范围('<,'>当来自可视模式时,vim 放在命令开头的范围)。您只想在视觉选择开始的行上运行该命令。您还必须使用\_.*而不是.*为了匹配换行符。

逐块选择

对于逐块选择,我认为没有一种相当方便的方式来操作它们。我编写了一个插件,通过提供一种在任何视觉选择上运行任意 Ex 命令的方法,就好像它是整个缓冲区内容一样,可以用来减少此类编辑的痛苦。

它可在https://github.com/intuited/visdo获得。目前还没有打包,而且它在 vim.org 上还没有,但是你可以git clone将它的内容(减去 README 文件)复制到你的 vimdir 中。

如果您使用vim-addon-manager,只需在您的 vim-addons 目录中克隆 visdo ,您随后就可以ActivateAddons visdo. 我已经请求将它添加到 VAM 插件存储库中,因此在某些时候您将能够免除克隆,只需执行ActivateAddons visdo.

该插件添加了一个:VisDo命令,该命令旨在作为另一个命令的前缀(类似于该命令的工作方式:tab:silent。运行带有VisDo前置的命令将导致该命令在仅包含视觉选择的当前内容的缓冲区上运行。命令完成后,缓冲区的内容将粘贴到原始缓冲区的可视选择中,并删除临时缓冲区。

因此,要使用 VisDo 完成 OP 的目标,您将执行以下操作(选择要反转的单词,并使用上面定义的 ReverseLine 命令):

:'<,'>VisDo ReverseLine


旧更新

......之前的更新如下......警告:冗长,有点过时,而且大多是不必要的......

OP 的编辑更清楚地表明,这里的目标是能够反转视觉选择中包含的单词,特别是字符视觉选择。

这绝对不是一项简单的任务。当我第一次开始使用 vim 时,事实上 vim 并没有让这种事情变得简单,这让我很困惑。ed我想这是因为它的根源仍然是其前身和后代的面向行的编辑功能。例如,:'<,'>s/.../.../即使您处于逐字符或逐块 ( ctrlv) 视觉选择模式,替换命令也将始终对整行起作用。

Vimscript 确实可以找到任何“标记”的列号,包括可视选择的开头 ( '<) 和可视选择的结尾 ( '>)。也就是说,据我所知,它的支持极限。没有直接的方法来获取视觉选择的内容,也没有办法用其他东西替换视觉选择。当然,您可以使用普通模式命令(yp)来完成这两件事,但是这个clobbers 注册并且有点混乱。但是你可以保存初始寄存器,然后在粘贴后恢复它们......

所以基本上你必须用一些极端的长度来处理部分线条,而这可以很容易地用整条线条来完成。我怀疑最好的方法是编写一个将视觉选择复制到新缓冲区中的命令,在其上运行一些其他命令,然后用结果替换原始缓冲区的视觉选择,删除临时缓冲区。这种方法理论上应该适用于逐字符和逐块选择,以及已经支持的逐行选择。但是,我还没有这样做。

这个 40 行代码块声明了一个ReverseCharwiseVisualWords可以从可视模式调用的命令。只有当逐字符的视觉选择完全在一行上时,它才会起作用。它的工作原理是获取包含视觉选择的整行(使用getline())在其选定部分上运行参数化转换函数(ReverseWords),然后将整个部分转换的行粘贴回去。回想起来,我认为可能值得走y/p路线以获得更有特色的东西。

" Return 1-based column numbers for the start and end of the visual selection.
function! GetVisualCols()
  return [getpos("'<")[2], getpos("'>")[2]]
endfunction

" Convert a 0-based string index to an RE atom using 1-based column index
" :help /\%c
function! ColAtom(index)
  return '\%' . string(a:index + 1) . 'c'
endfunction

" Replace the substring of a:str from a:start to a:end (inclusive)
" with a:repl
function! StrReplace(str, start, end, repl)
  let regexp = ColAtom(a:start) . '.*' . ColAtom(a:end + 1)
  return substitute(a:str, regexp, a:repl, '')
endfunction

" Replace the character-wise visual selection
" with the result of running a:Transform on it.
" Only works if the visual selection is on a single line.
function! TransformCharwiseVisual(Transform)
  let [startCol, endCol] = GetVisualCols()

  " Column numbers are 1-based; string indexes are 0-based
  let [startIndex, endIndex] = [startCol - 1, endCol - 1]

  let line = getline("'<")
  let visualSelection = line[startIndex : endIndex]
  let transformed = a:Transform(visualSelection)
  let transformed_line = StrReplace(line, startIndex, endIndex, transformed)

  call setline("'<", transformed_line)
endfunction

function! ReverseWords(words)
  return join(reverse(split(a:words)))
endfunction

" Use -range to allow range to be passed
" as by default for commands initiated from visual mode,
" then ignore it.
command! -range ReverseCharwiseVisualWords
      \ call TransformCharwiseVisual(function('ReverseWords'))

更新 2

y事实证明,用and做事p要简单得多,所以我想我也会发布它。警告:我没有对此进行彻底的测试,因此可能存在边缘情况。

这个函数替换TransformCharwiseVisual了前一个代码块中的(以及它的一些依赖项)。从理论上讲,它也应该适用于逐块选择 - 由传递的Transform函数来使用行分隔符做适当的事情。

function! TransformYankPasteVisual(Transform)
  let original_unnamed = [getreg('"'), getregtype('"')]
  try
    " Reactivate and yank the current visual selection.
    normal gvy
    let @" = a:Transform(@")
    normal gvp
  finally
    call call(function('setreg'), ['"'] + original_unnamed)
  endtry
endfunction

那么你可以添加第二个命令声明

command! -range ReverseVisualWords
      \ call TransformYankPasteVisual(function('ReverseWords'))

切向相关的血腥细节

请注意,这里使用的高级函数的实用性受到以下事实的限制:在 vimscript 中没有(简单或已建立的)声明内联函数或代码块的方法。如果该语言不打算以交互方式使用,则不会有这样的限制。您可以编写一个函数,将其字符串参数替换为字典函数声明并返回该函数。但是,字典函数不能使用正常的调用语法调用,必须传递给call call(dictfunct, args, {}).

注意:上面给出的更新的更新已经废弃了上面的代码。请参阅旧更新之前的各个部分,以获得更简洁的方法来执行此操作。

于 2011-04-03T21:04:23.433 回答
1

这是调用 Ruby 的一种方法。选择要反转的行后,您可以在命令模式下执行此操作来替换它:

!ruby -e 'puts ARGF.read.strip.split(/\b/).reverse.join'
于 2011-04-03T23:20:23.013 回答
1

感谢您的回答和大量尝试,我自己找到了解决方案:)

这有效:

function! Test()
exe 'normal ' . 'gv"ay'
let r = join(reverse(split(getreg('a'))))
let @a = r
exe 'normal ' . 'gv"ap'
endfunction

没想到我能写出这样的功能:)

于 2011-04-04T14:41:30.743 回答
1

也许:

:s/\v(.*) (.*) (.*) (.*)/\4 \3 \2 \1/

当然,您可能需要在第一部分更具体才能找到那个特定的句子。通常,您可以将匹配组称为 \number,其中 \0 是整个匹配。

于 2011-04-03T20:55:57.823 回答