Substitua uma série de marcadores de asterisco por uma lista numerada


16

Imagine que tenho o seguinte texto:

some random stuff
* asdf
* foo
* bar
some other random stuff

Quero substituir os marcadores de asterisco por números, da seguinte forma:

some random stuff
1. asdf
2. foo
3. bar
some other random stuff

Como isso pode ser feito no vim?


Por que você não usa plugins? Um semelhante é increment.vim em Github
SibiCoder

É tão incrível e legal que todo mundo fez suas respostas incrementarem os números, mas como o Markdown os numerará para você, por que não apenas fazer todos eles 1.? Então :%s/^* /1. /faria isso. Isso parece muito menos trabalho.
pintos

Respostas:


14

Você pode tentar o seguinte comando:

:let c=0 | g/^* /let c+=1 | s//\=c.'. '

Primeiro, inicializa a variável c( let c=0), depois executa o comando global gque procura o padrão ^*(um início de linha, seguido por um asterisco e um espaço).

Sempre que uma linha que contém esse padrão é encontrada, o comando global executa o comando:
let c+=1 | s//\=c.'. '
Incrementa a variável c( let c+=1) e ( |) substitui ( s) o padrão pesquisado anterior ( //) pela avaliação de uma expressão ( \=):
o conteúdo da variável cconcatenada ( .) com a string'. '


Se você não quiser modificar todas as linhas do seu buffer, mas apenas um parágrafo específico, poderá passar um intervalo para o comando global. Por exemplo, para modificar apenas as linhas cujo número está entre 5 e 10:

:let c=0 | 5,10g/^* /let c+=1 | s//\=c.'. '

Se você tiver um arquivo contendo várias listas semelhantes que deseja converter, por exemplo, algo como isto:

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                

Você pode fazer isso com o seguinte comando:

:let [c,d]=[0,0] | g/^* /let [c,d]=[line('.')==d+1 ? c+1 : 1, line('.')] | s//\=c.'. '

É apenas uma variante do comando anterior, que redefine a variável cquando você alterna para outra lista. Para detectar se você está em outra lista, a variável dé usada para armazenar o número da última linha em que uma substituição foi feita.
O comando global compara o número da linha atual ( line('.')) com d+1. Se eles são iguais, significa que estamos na mesma lista de antes, por isso cé incrementado ( c+1); caso contrário, significa que estamos em uma lista diferente, por isso cé redefinido ( 1).

Dentro de uma função, o comando let [c,d]=[line('.')==d+1 ? c+1 : 1, line('.')]pode ser reescrito assim:

let c = line('.') == d+1 ? c+1 : 1
let d = line('.')

Ou assim:

if line('.') == d+1
    let c = c+1
else
    let c = 1
endif
let d = line('.')

Para salvar algumas teclas, você também pode definir o comando personalizado :NumberedLists, que aceita um intervalo cujo valor padrão é 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.'. '

Quando :NumberedListsserá executado <line1>e <line2>será automaticamente substituído pelo intervalo usado.

Portanto, para converter todas as listas no buffer, digite: :NumberedLists

Somente as listas entre as linhas 10 e 20: :10,20NumberedLists

Somente a seleção visual: :'<,'>NumberedLists


Para mais informações, veja:

:help :range
:help :global
:help :substitute
:help sub-replace-expression
:help list-identity    (section list unpack)
:help expr1
:help :command

9

Isso funciona apenas com uma versão recente do Vim (que possui :h v_g_CTRL-A):

  1. Block-seleccionar as balas lista ( *) e substituí-los com 0(cursor está na primeira *): Ctrl-v j j r 0.
  2. Selecione novamente o bloco anterior e incremente com o contador :gv g Ctrl-a

... e é isso :)


(Se você deseja obter um ponto após cada número, altere a 1ª etapa para Ctrl-v j j s 0 . Esc:)


9

Selecione visualmente as linhas e execute este comando de substituição:

:'<,'>s/*/\=line('.') - line("'<") + 1 . '.'

Ver :help sub-replace-expression, :help line()e :help '<.

Para evitar ter que selecionar as linhas, pesquisas para trás e para frente com deslocamentos podem ser usadas para especificar o intervalo de substituição como este:

:?^[^*]?+1,/^[^*]/-1s/*/\=line('.') - search('^[^[:digit:]]', 'bn') . '.'

Vejo :help cmdline-ranges


2

Outra maneira:

:let n = 1 | g/^* /s//\=printf('%d. ', n)/g | let n = n + 1

0

Você também pode definir operadores personalizados

Você pode mapeá-los para as seqüências principais '*e '#. As marcas *e #não existem, portanto você não substituirá nenhuma funcionalidade padrão. A razão para escolher 'como prefixo é obter algum tipo de mnemônica. Você está adicionando um sinal / marca na frente de algumas linhas. E, geralmente, para ir a uma marca, você usa o prefixo '.

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>

Também funciona no modo visual.
Os comandos ex são bons para scripts, mas para um uso interativo, um operador normal provavelmente é melhor, porque você pode combiná-lo com qualquer movimento ou objeto de texto.

Por exemplo, você pode alternar uma lista prefixada com asteriscos ou sinais de menos dentro do parágrafo atual pressionando '*ip. Aqui, '*é um operador e ipé o objeto de texto no qual trabalha.

E faça o mesmo para uma lista prefixada com números nas próximas 10 linhas pressionando '#10j. Aqui, '#existe outro operador e 10jé um movimento que cobre as linhas nas quais o operador trabalha.

O outro benefício do uso de um operador personalizado é que você pode repetir sua última edição com o comando dot.

insira a descrição da imagem aqui

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.