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?

Brennan Vincent
fonte
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
saginaw
fonte
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:)

VanLaser
fonte
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

djjcast
fonte
2

Outra maneira:

:let n = 1 | g/^* /s//\=printf('%d. ', n)/g | let n = n + 1
Cylian
fonte
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

user9433424
fonte