假设我有以下文字:
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命令重复上一个版本。