Vim:反转字符串(按字词)

Rem*_*man 10 regex string vim words

这是我的字符串:

"this is my sentence"

我想有这个输出:

"sentence my is this"

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

谁能帮我?

int*_*ted 16

目前还不完全清楚上下文是什么:你可能正在讨论缓冲区中的行中的文本或存储在VimScript变量中的字符串.

注意:对问题的不同解释导致了各种方法和解决方案.
有一些" 旧更新 "开始于大约一半,已经或多或少地被那个部分上面提到的插件所取代.我把它们留了下来因为它们可能会为某些人提供有用的信息.

全线更换

因此,要将当前行中的文本存储在vimscript变量中的当前行中

let words = getline('.')
Run Code Online (Sandbox Code Playgroud)

然后改变他们的顺序,你就是这样做的

let words = join(reverse(split(words)))
Run Code Online (Sandbox Code Playgroud)

如果你想用反转的单词替换当前行,你可以

call setline('.', words)
Run Code Online (Sandbox Code Playgroud)

你可以用一个有点不可理喻的线来做到这一点

call setline('.', join(reverse(split(getline('.')))))
Run Code Online (Sandbox Code Playgroud)

甚至定义一个用它来执行的命令

command! ReverseLine call setline('.', join(reverse(split(getline('.')))))
Run Code Online (Sandbox Code Playgroud)

部分线(字符方式)选择

正如"旧更新"部分所述,在字符或块状视觉选择上运行常规命令 - 前者是OP想要在这里做的 - 可能相当复杂.:substitute即使只使用字符方式的视觉选择(以未移位的方式启动v)选择了部分行,Ex命令也会在整行上运行.

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

:s/\%V.*\%V./\=join(reverse(split(submatch(0))))/
Run Code Online (Sandbox Code Playgroud)

其中,

  • \%V在RE内部匹配视觉选择的某些部分.显然,这不会在选择中的最后一个字符后延伸:省略最后.一个字符将排除最后一个字符.
  • \=在替换开始时表示它将被评估为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工作方式).运行带有VisDoprepended 的命令将导致该命令在仅包含可视选择的当前内容的缓冲区上运行.命令完成后,缓冲区的内容将粘贴到原始缓冲区的可视选择中,并删除临时缓冲区.

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

:'<,'>VisDo ReverseLine
Run Code Online (Sandbox Code Playgroud)

旧的更新

...以前的更新跟随...警告:冗长,有点过时,而且大部分是不必要的......

OP的编辑使得更清楚的是,这里的目标是能够反转视觉选择中包含的单词,特别是字符方式的视觉选择.

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

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

因此,基本上你必须采用极端的长度来处理部分线条,这可以通过整条线条轻松完成.我怀疑最好的方法是编写一个命令,将视觉选择复制到一个新缓冲区,在其上运行一些其他命令,然后用结果替换原始缓冲区的视觉选择,删除临时缓冲区.理论上,这种方法既适用于字符选择,也适用于逐块选择,也适用于已经支持的线性选择.但是,我还没有这样做.

这个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'))
Run Code Online (Sandbox Code Playgroud)

更新2

事实证明,做事情y并且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
Run Code Online (Sandbox Code Playgroud)

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

command! -range ReverseVisualWords
      \ call TransformYankPasteVisual(function('ReverseWords'))
Run Code Online (Sandbox Code Playgroud)

切线相关的血腥细节

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

注意:上面给出的更新的更新废弃了上面的代码.请参阅旧更新之前的各个部分,以获得更简洁的方法.