在选择或匹配项的每行中插入一个递增数字


10

我有一个问题,我可以想到两种通用的解决方法,但是我不知道这两种方法的具体细节。

...
Level 1:    cũng    also
Level 1:    và      and
Level 1:    như     like; such as
Level 2:    các     plural marker
Level 2:    của     belonging to
...

对于以“ Level n”开头的每一行,我想插入一个以“ 01”开头的数字。为了简单起见,让我们在数字前加上数字。

方法1:手动选择同一级别的所有行。调用魔术,我很快就会学习。

方法2:编写搜索并替换,以给定级别匹配所有行,在每个匹配项中,替换文本中均包含一个数字,该数字在每个匹配项中增加1。

在StackOverflow或其他Vim站点发现了类似的问题,但每个问题似乎都具有以下一个或多个问题:

  1. 关于插入当前行号,而不是插入任意但递增的号码。
  2. 不要将数字零填充。
  3. 在Windows 7上运行的Vim 7.4上的选择实际上不起作用(这些都会导致错误E481: No range allowed。)

我在Windows中使用mswin.vim运行gVim,但是最好在所有自定义Vim安装中使用的解决方案是最佳的。


我所想到的最佳标签并不存在:选择范围,因此可以随意重新标签或创建其中一个标签。
hippietrail 2015年

1
这个插件不是解决您问题的完整解决方案,但是对于添加数字列非常有用:VisIncr。文档在这里。FWIW。
lcd047

Answers:


17

类似于https://vi.stackexchange.com/a/818/227的答案,您可以使用global命令。

使用它,您可以指示vim搜索与模式匹配的行,然后在其上执行命令。

对于您的情况,您希望将文本添加到以“ Level N:”开头的行之前,因此我们的全局命令可以是

:g/^Level \d:/{COMMANDS}

使用替代命令(正则表达式替换)作为命令

命令更加有趣。我通常喜欢对正则表达式进行替换,因为这样易于使用变量。

您的问题的例子

:let i = 1 | g/^Level \d:/s/^/\=printf("%02d ", i)/ | let i = i+1

怎么运行的

在替换命令的替换部分中可以是一个表达式。

我们要做的第一件事是将变量设置为i起始数字。我选择了1,但任何数字都可以。let i = 1

然后,我们运行全局命令,该命令将我们设置为在匹配的行上执行操作。 g/^Level \d:/

我们将使我们的全局命令插入值,并使用替换命令和let命令增加计数器。 s/^/\=printf("%02d ", i)/ | let i = i+1

替换命令的正则表达式找到该行的开头^并将其替换为表达式,而我们的表达式将是格式化打印的结果。像C语言一样,vim的printf带有格式设置参数。%02d表示将参数转换为十进制数d,至少占用2个空格,2并填充0 0。有关详细信息和其他转换选项(包括浮点格式),请参见:help printf。我们给printf我们的计数变量i,它给我们01第一次,02第二次等。替换命令使用它来替换行的开头,从而有效地将printf的结果插入开头。

请注意,我在d:之后放置了一个空格"%02d "。您没有在问题中提出要求(并且我没有看到示例输出),但是我怀疑您想将数字与单词“ Level”分开。从给printf的字符串中删除空格,以使插入的数字紧靠在Level中的L旁边。

最后,let i = i + 1每次替换后,我们的计数器都会增加。

这通常可用于用任意功能数据替换与其他条件匹配的部分线。

使用组合的普通命令

这对于简单的插入或复杂的编辑很有用。像替换一样,我们将使用global进行匹配,但是我们将执行一系列操作,就像由用户键入内容一样,而不是使用正则表达式替换。

您的问题的例子

:let i = 1 | g/^Level \d:/execute "normal! I" . printf("%02d ", i) | let i = i+1

怎么运行的

所使用的值与替代项非常相似(我们仍在使用printf格式化我们的数字以使其用2位数字填充0),但是操作不同。

在这里,我们使用execute命令,该命令接受一个字符串并将该字符串作为ex命令(:help :exe)运行。我们构造了一个字符串,该字符串将“ normal!I”与我们的数据结合在一起,第一次将是“ normal!I01”,第二次将是“ normal!I02”,依此类推。

normal命令就像在普通模式下一样执行操作。在此示例中,我们的常规命令是I,它会插入到行的开头。如果使用过dd它将删除该行,o将在匹配的行之后打开一个新行。就像您I在普通模式下自己键入(或执行其他任何操作)一样。我们使用!after normal来确保没有映射妨碍我们。请参阅:help :normal

然后插入的是我们的printf的值,如使用替代的第一个示例中所示。

此方法可能比正则表达式更奇特,因为您可以执行类似的操作execute "normal! ^2wy" . i . "th$p",该操作将转到文本的开头^,向前移动两个单词2w,拉动直到第i个“ h”字符y" . i . "th,移至行的末尾$并粘贴p

这几乎就像运行宏一样,但是实际上并没有用完寄存器,而是可以合并任何表达式中的字符串。我发现这非常强大。

每个级别都有自己的计数器的方法

您可能希望每个级别都有自己的计数器。如果您提前知道最大级别数,则可以执行以下操作(添加额外的代码来找到最大级别可能并不难,但是会使答案变得太长。这会变得很长)。

首先,让我们释放i,以防万一我们已经将其用作整数。我们无法将我转换为列表,我们必须以这种方式创建它。

:unlet! i

接下来,将i设置为包含级别数的列表。您在问题中显示了2,但假设其中有10个是有趣的。由于列表索引是基于0的,并且我不想像您的列表那样基于1进行校正,因此我们将只创建足够的元素(11),并且永远不要使用0索引。

:let j = 0
:let i = []
:while j < 11 | let i += [1] | let j += 1 | endwhile

接下来,我们需要一种获取级别编号的方法。幸运的是,替代函数也可以用作函数,因此我们将其命名为行并提取级别号substitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")

由于i现在是一个11 1s 的列表(每个索引是该级别的计数器),因此我们现在可以调整上述两个示例中的任何一个以使用此替换的结果:

通过替代命令:

:unlet! i | unlet! j | let j = 0 | let i = [] | while j < 11 | let i += [1] | let j += 1 | endwhile
:g/^Level \d:/let ind=str2nr(substitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")) | s/^/\=printf("%02d ", i[ind])/ | let i[ind] += 1

通过普通命令:

:unlet! i | unlet! j | let j = 0 | let i = [] | while j < 11 | let i += [1] | let j += 1 | endwhile
:g/^Level \d:/let ind=str2nr(substitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")) | execute "normal! I" . printf("%02d ", i[ind]) | let i[ind] += 1

输入示例:

Level 1: stuff

Level 1: Stuff

Some text
Level 3: Other

Level 1: Meh

Level 2: More

输出示例:

01 Level 1: stuff

02 Level 1: Stuff

Some text
01 Level 3: Other

03 Level 1: Meh

01 Level 2: More

哇,非常百科。我只阅读了第一部分,感觉比过去几年我从中学到了更多关于Vim的知识。这种答案使Stack Exchange优于一般的问答站点,并且显示了具有Vim特定SE的好处。
hippietrail 2015年

3

您可以从https://stackoverflow.com/a/4224454/15934构建,以零填充您的数字。

" A reminder: how to start numbers at the first line
:'<,'>s/^\s*\zs/\=(line('.') - line("'<")+1).'. '

但是为了简化填充数字的操作,我将使用一对函数和一个命令:

command!  -range=% -nargs=? PrependNumbers <line1>,<line2>call s:Prepend(<args>)

function! s:ReplExpr(nb_digits, number)
  return repeat('0', a:nb_digits - strlen(a:number)).a:number
endfunction

function! s:Prepend(...) range
  let pattern = a:0 > 0 ? '\ze'. a:1 : '^'
  let nb_values = (a:lastline - a:firstline) + 1
  let nb_digits = strlen(nb_values)
  exe ':'.a:firstline.','a:lastline.'s#'.pattern.'#\=s:ReplExpr(nb_digits, 1+ line(".")-'.a:firstline.')." "#'
endfunction

然后,选择您的行并键入:PrependNumber(您将看到:'<,'>PrependNumber。[注意:该命令带有一个可选参数:将在其前面插入数字的模式]


但这不是line(".")指“使用当前行号”吗?那是我的问题1.我可以找到以前的答案。
hippietrail 2015年

1
是的。这就是为什么我减去范围中第一行的行号。
卢克·赫米特

啊,好的,我还没有测试过,因为我什至不记得如何在Vim中输入脚本……必须首先阅读该内容(-:
hippietrail 2015年

1
在Windows下,将代码复制粘贴到$HOME/vimfiles/plugin/whatevernameyouwish.vim。甚至$HOME/_vimrc(第一次)甚至是您的(Windows文件名)。如果不确定计算机上的$ HOME是什么,请询问vim它认为它是什么->:echo $HOME
Luc Hermitte 2015年

1
您甚至不需要:source将vim脚本正确安装在正确的目录中({rtp} / plugin或{rtp} / ftplugin / {filetype} / ftplugin用于特定于文件类型的插件)。:source是十年半前我们必须玩的游戏。
卢克·赫米特

2

您可以通过录制宏来解决它。从原始文件开始,在第一个实例之前添加数字。

01 Level 1:    cũng    also
Level 1:    và      and
Level 1:    như     like; such as
Level 2:    các     plural marker
Level 2:    của     belonging to

将光标移到01,选择并用进行拉动yiw。现在您要记录这一点的动作。

qq/^Level 1<CR>P<C-A>A<space><esc>0yiwq

  • qq 在寄存器q中启动宏
  • /^Level 1<CR> 搜索以“ Level 1”开头的行
  • P 在光标之前粘贴(包含您的电话号码)
  • <C-A> 增加数字
  • A<space><esc> 在数字后面插入一个空格
  • 0 移到开始
  • yiw 提取当前号码
  • q 结束宏

然后使用重复此宏@q


1
<C-A>控制+吗?这样就可以选择Windows上Vim中的所有内容。
hippietrail 2015年

它是Control + a。在vim中,它应该增加一个数字。如果您已将键绑定更改为执行Windows操作而不是执行Vim操作,则您将无法执行人们在此处发布的大多数操作。
jecxjo 2015年

由于有人的无限智慧,Windows的Vim版本没有附带不同的绑定。我只是使用香草的现成绑定。二十年来,我从未改变过Vim键绑定,我记得(-:
hippietrail 2015年

并非如此,我的Windows安装具有正常的vim绑定。您是否可能安装了其他人的vim版本?还是可以在“简易”模式下运行vim?我相信Windows会以普通和简易模式选项安装桌面图标。
jecxjo 2015年

3
不,这是因为Windows中的默认安装会加载mswin.vim。如果您创建自己的vimrc而不加载mswin.vim,那么您将获得正常的vim绑定。我为所有安装(Linux,Mac和Windows)保留一个vimrc,从不处理mswin.vim。有关此问题查看更多信息stackoverflow.com/questions/289681/...
jecxjo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.