假设我有以下文字:
some random stuff
* asdf
* foo
* bar
some other random stuff
我想用数字代替星号项目符号,如下所示:
some random stuff
1. asdf
2. foo
3. bar
some other random stuff
如何在vim中完成此操作?
1.
呢?这样:%s/^* /1. /
就可以了。看来工作量要少得多。
假设我有以下文字:
some random stuff
* asdf
* foo
* bar
some other random stuff
我想用数字代替星号项目符号,如下所示:
some random stuff
1. asdf
2. foo
3. bar
some other random stuff
如何在vim中完成此操作?
1.
呢?这样:%s/^* /1. /
就可以了。看来工作量要少得多。
Answers:
您可以尝试以下命令:
:let c=0 | g/^* /let c+=1 | s//\=c.'. '
首先,它初始化变量c
(let c=0
),然后执行g
用于查找模式的全局命令^*
(行的开头,后跟一个星号和一个空格)。
每当找到包含此模式的行时,global命令都会执行以下命令:
let c+=1 | s//\=c.'. '
递增变量c
(let c+=1
),然后(|
)将(s
)先前搜索的模式(//
)替换为表达式(\=
)的求值():
变量的内容c
串联(.
)与字符串'. '
如果您不想修改缓冲区中的所有行,而只想修改特定的段落,则可以将范围传递给global命令。例如,仅修改数字在5到10之间的行:
:let c=0 | 5,10g/^* /let c+=1 | s//\=c.'. '
如果您的文件包含要转换的多个类似列表,例如,如下所示:
some random stuff some random stuff
* foo 1. foo
* bar 2. bar
* baz 3. baz
some other random stuff some other random stuff
==>
some random stuff some random stuff
* foo 1. foo
* bar 2. bar
* baz 3. baz
* qux 4. qux
some other random stuff some other random stuff
您可以使用以下命令进行操作:
:let [c,d]=[0,0] | g/^* /let [c,d]=[line('.')==d+1 ? c+1 : 1, line('.')] | s//\=c.'. '
这只是上一个命令的变体,c
当您切换到另一个列表时,该命令会重置变量。为了检测您是否在另一个列表中,该变量d
用于存储进行替换的最后一行的编号。
全局命令将当前行号(line('.')
)与进行比较d+1
。如果它们相同,则表示我们与以前在同一列表中,因此c
增加了(c+1
),否则意味着我们在不同的列表中,因此c
将其重置了(1
)。
在函数内部,let [c,d]=[line('.')==d+1 ? c+1 : 1, line('.')]
可以这样重写命令:
let c = line('.') == d+1 ? c+1 : 1
let d = line('.')
或像这样:
if line('.') == d+1
let c = c+1
else
let c = 1
endif
let d = line('.')
要保存一些击键,您还可以定义custom命令:NumberedLists
,该命令接受默认值为1,$
(-range=%
)的范围:
command! -range=% NumberedLists let [c,d]=[0,0] | <line1>,<line2>g/^* /let [c,d]=[line('.')==d+1 ? c+1 : 1, line('.')] | s//\=c.'. '
当:NumberedLists
将被执行,<line1>
并<line2>
会与你使用的范围被自动替换。
因此,要转换缓冲区中的所有列表,请输入: :NumberedLists
仅第10至20行之间的列表: :10,20NumberedLists
仅视觉选择: :'<,'>NumberedLists
有关更多信息,请参见:
:help :range
:help :global
:help :substitute
:help sub-replace-expression
:help list-identity (section list unpack)
:help expr1
:help :command
您还可以定义自定义运算符
您可以将它们映射到键序列'*
和'#
。标记*
和#
不存在,因此您不会覆盖任何默认功能。选择'
作为前缀的原因是获得某种助记符。您要在某些行的前面添加符号/标记。通常,要标记一个标记,请使用前缀'
。
fu! s:op_list_bullet(...) abort range
if a:0
let [lnum1, lnum2] = [line("'["), line("']")]
else
let [lnum1, lnum2] = [line("'<"), line("'>")]
endif
if !empty(matchstr(getline(lnum1), '^\s*\d\s*\.'))
let pattern = '\d\s*\.\s\?'
let replacement = '* '
elseif count(['-', '*'], matchstr(getline(lnum1), '\S'))
let pattern = '\v\S\s*'
let replacement = ''
else
let pattern = '\v\ze\S'
let replacement = '* '
endif
let cmd = 'keepj keepp %s,%s s/%s/%s'
sil exe printf(cmd, lnum1, lnum2, pattern, replacement)
endfu
fu! s:op_list_digit(...) abort range
let l:c = 0
if a:0
let [lnum1, lnum2] = [line("'["), line("']")]
else
let [lnum1, lnum2] = [a:firstline, a:lastline]
endif
if count(['-', '*'], matchstr(getline(lnum1), '\S'))
let pattern = '\S\s*'
let replacement = '\=l:c.". "'
elseif !empty(matchstr(getline(lnum1), '^\s*\d\s*\.'))
let pattern = '\d\s*\.\s\?'
let replacement = ''
else
let pattern = '\v^\s*\zs\ze\S'
let replacement = '\=l:c.". "'
endif
let cmd = 'keepj keepp %s,%s g/%s/let l:c = line(".") == line("'']")+1 ?
\ l:c+1 : 1 |
\ keepj keepp s/%s/%s'
sil exe printf(cmd, lnum1, lnum2, pattern, pattern, replacement)
endfu
nno <silent> '* :<C-U>set opfunc=<SID>op_list_bullet<CR>g@
nno <silent> '** :<C-U>set opfunc=<SID>op_list_bullet
\<Bar>exe 'norm! ' . v:count1 . 'g@_'<CR>
xno <silent> '* :call <SID>op_list_bullet()<CR>
nno <silent> '# :<C-U>set opfunc=<SID>op_list_digit<CR>g@
nno <silent> '## :<C-U>set opfunc=<SID>op_list_digit
\<Bar>exe 'norm! ' . v:count1 . 'g@_'<CR>
xno <silent> '# :call <SID>op_list_digit()<CR>
它也可以在可视模式下工作。
Ex命令适合脚本编写,但对于交互式使用,普通运算符可能更好,因为您可以将其与任何运动或文本对象结合使用。
例如,您可以通过点击在当前段落内切换以星号或减号为前缀的列表'*ip
。在这里,'*
是一个运算符,并且ip
是它起作用的文本对象。
并点击,对列表的后10行中带有数字的列表执行相同的操作'#10j
。在这里,'#
是另一位操作员,并且10j
是覆盖该操作员所从事的工作的动作。
使用自定义运算符的另一个好处是,您可以使用dot命令重复上一个版本。