Pesquisa global e substituição no Vim, começando na posição do cursor e envolvendo

112

Quando procuro com o / comando do modo normal:

/\vSEARCHTERM

O Vim inicia a pesquisa a partir da posição do cursor e continua para baixo, indo até o topo. No entanto, quando procuro e substituo usando o :substitutecomando:

:%s/\vBEFORE/AFTER/gc

Em vez disso, o Vim começa no início do arquivo.

Existe uma maneira de fazer com que o Vim execute a pesquisa e substituição começando da posição do cursor e passando para o topo quando chega ao final?

Basteln
fonte
2
\vpattern- padrão 'muito mágico': caracteres não alfanuméricos são interpretados como símbolos regex especiais (nenhum escape é necessário)
Carlos Araya
qual é o ponto para começar da posição atual e envolver o arquivo em vez de usar apenas %? não vejo nenhum uso razoável para que você esteja examinando todo o arquivo de qualquer maneira.
tymik de
@tymik O objetivo era começar a pesquisar a partir do cursor e passar por cada resultado passo a passo. Mas eu não uso o Vim há anos.
basteln de

Respostas:

50

1.  Não é difícil alcançar o comportamento usando uma substituição em duas etapas:

:,$s/BEFORE/AFTER/gc|1,''-&&

Primeiro, o comando de substituição é executado para cada linha, começando da atual até o final do arquivo:

,$s/BEFORE/AFTER/gc

Em seguida, esse :substitutecomando é repetido com o mesmo padrão de pesquisa, string de substituição e sinalizadores, usando o :& comando (consulte :help :&):

1,''-&&

O último, entretanto, realiza a substituição no intervalo de linhas da primeira linha do arquivo para a linha onde a marca de contexto anterior foi definida, menos um. Como o primeiro :substitutecomando armazena a posição do cursor antes de iniciar as substituições reais, a linha endereçada por  ''é a linha que era a atual antes que o comando de substituição fosse executado. (O '' endereço refere-se à ' pseudo-marca; consulte :help :rangee :help ''para obter detalhes.)

Observe que o segundo comando (após o | separador de comando - consulte :help :bar) não requer nenhuma alteração quando o padrão ou sinalizadores são alterados no primeiro.

2.  Para economizar alguma digitação, a fim de trazer o esqueleto do comando de substituição acima na linha de comando, pode-se definir um mapeamento de modo normal, assim:

:noremap <leader>cs :,$s///gc\|1,''-&&<c-b><right><right><right><right>

A <c-b><right><right><right><right>parte final é necessária para mover o cursor para o início da linha de comando ( <c-b>) e, em seguida, quatro caracteres para a direita ( <right> × 4), colocando-o entre os dois primeiros sinais de barra, pronto para o usuário começar a digitar o padrão de pesquisa . Assim que o padrão desejado e a substituição estiverem prontos, o comando resultante pode ser executado pressionando Enter.

(Pode-se considerar ter em //vez de ///no mapeamento acima, se preferir digitar o padrão, então digite a barra separadora, seguida pela string de substituição, em vez de usar a seta para a direita para mover o cursor sobre uma barra separadora já presente começando a peça de reposição.)

ib.
fonte
1
O único problema com esta solução é que as opções last (l) e quit (q) não interrompem a execução do segundo comando substitute
Steve Vermeulen
@eventualEntropy Veja minha solução abaixo sobre como solicitar outro 'q' pressione.
q335r49
2
Não se esqueça (como eu fiz) de escapar do tubo se você estiver colocando isso em um mapeamento de teclas.
jazzabeanie
11
Oh meu Deus, o que todos esses personagens arbitrários ainda significam. Como você aprendeu isso?
guaxinim rochoso de
2
@Tropilio: Então você pode tentar algo como isto: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Ele é :,$s///gc|1,''-&&colocado na linha de comando com o cursor entre os dois primeiros sinais de barra. Depois de digitar o padrão e substituição desejados, você pode executar o comando resultante pressionando Enter .
ib.
174

Você já está usando um intervalo,, %que é uma forma abreviada de 1,$significar o arquivo inteiro. Para ir da linha atual até o final que você usa .,$. O período significa linha atual e $significa a última linha. Portanto, o comando seria:

:.,$s/\vBEFORE/AFTER/gc

Mas a .linha ou atual pode ser assumida, portanto, pode ser removida:

:,$s/\vBEFORE/AFTER/gc

Para obter mais ajuda, consulte

:h range
Peter Rincker
fonte
Obrigado, eu já sabia disso. Quero que a pesquisa e substituição seja concluída assim que chegar ao final do documento. Com:., $ S não faz isso, apenas diz "Padrão não encontrado".
basteln
7
Para "as próximas dez linhas":10:s/pattern/replace/gc
aqui
10
embora não seja o que o OP estava procurando, isso foi muito útil para mim, obrigado!
Tobias J
51

%é um atalho para 1,$
(ajuda do Vim => :help :%igual a 1, $ (o arquivo inteiro).)

. é a posição do cursor para que você possa fazer

:.,$s/\vBEFORE/AFTER/gc

Para substituir do início do documento até o cursor

:1,.s/\vBEFORE/AFTER/gc

etc

Eu sugiro fortemente que você leia o manual sobre range, :help rangepois quase todos os comandos funcionam com range.

mb14
fonte
Obrigado, eu já sabia disso. Quero que a pesquisa e substituição seja concluída assim que chegar ao final do documento. Com:., $ S não faz isso, apenas diz "Padrão não encontrado".
basteln
@basteln: ocorreu um erro de digitação na postagem // você copiou e colou?
ver
Então você quer substituir TUDO, mas como você quer pedir uma confirmação, você quer começar da posição atual. Basta fazer isso duas vezes então.
mb14
você pode usar n e & em vez disso.
mb14
hmm, acho que n e & é a melhor solução, embora não seja exatamente como deveria ser (com o prompt sim / não em todas as correspondências). Mas definitivamente bom o suficiente, obrigado! :)
basteln
4

FINALMENTE encontrei uma solução para o fato de que sair da pesquisa volta ao início do arquivo sem escrever uma função enorme ...

Você não acreditaria quanto tempo demorei para inventar isso. Simplesmente adicione um prompt se deseja quebrar: se o usuário pressionar qnovamente, não quebra. Então, basicamente, saia da pesquisa tocando em qqvez de q! (E se você quiser embrulhar, basta digitar y.)

:,$s/BEFORE/AFTER/gce|echo 'Continue at beginning of file? (y/q)'|if getchar()!=113|1,''-&&|en

Na verdade, eu mapeei isso para uma tecla de atalho. Assim, por exemplo, se você deseja pesquisar e substituir todas as palavras sob o cursor, começando da posição atual, por q*:

exe 'nno q* :,$s/\<<c-r>=expand("<cword>")<cr>\>//gce\|echo "Continue at beginning of file? (y/q)"\|if getchar()==121\|1,''''-&&\|en'.repeat('<left>',77)
q335r49
fonte
Na minha opinião, esta abordagem - inserir prompt adicional entre os dois comandos propostos em minha resposta - adiciona complexidade desnecessária sem melhorar a usabilidade. Na versão original , se você sair do primeiro comando de substituição (com q, lou Esc ) e não quiser continuar a partir do início, você já pode pressionar qou Esc novamente para sair do segundo comando imediatamente. Ou seja, a adição proposta do prompt parece duplicar efetivamente a funcionalidade já presente.
ib.
Cara ... você EXPERIMENTE sua abordagem? Digamos que eu queira substituir as próximas 3 ocorrências de "let" por "unlet", e então - esta é a chave - continuar editando o parágrafo . Sua abordagem "Pressione y, y, y, agora pressione q. Agora você começa a pesquisar na primeira linha do parágrafo. Pressione q novamente para sair. Agora volte para onde estava antes para continuar editando. Ok, g ;. Então, basicamente: yyyqq ... fica confuso ... g; (ou u ^ R) Meu método: yyyqq
q335r49
O método ideal é, obviamente yyyq, continuar editando, sem saltos desorientadores. Mas yyyqqé quase tão bom, se você considerar um toque duplo, praticamente um único toque em termos de facilidade de uso.
q335r49
Nem o comando original nem sua versão com prompt retornam o cursor à sua posição anterior naquele cenário. O primeiro deixa o usuário na primeira ocorrência do padrão de cima (onde estava no momento de pressionar qpela segunda vez). O último deixa o cursor na última ocorrência que foi substituída (logo antes de a primeira qser pressionada).
ib.
1
Na versão original , se é preferível para retornar automaticamente o cursor para a sua posição antes (sem a tipagem utilizador Ctrl + O ), um pode simplesmente acrescentar o norm!``comando: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.
3

Aqui está algo muito aproximado que aborda as preocupações sobre envolver a pesquisa com a abordagem de duas etapas ( :,$s/BEFORE/AFTER/gc|1,''-&&) ou com um intermediário "Continuar no início do arquivo?" abordagem:

" Define a mapping that calls a command.
nnoremap <Leader>e :Substitute/\v<<C-R>=expand('<cword>')<CR>>//<Left>

" And that command calls a script-local function.
command! -nargs=1 Substitute call s:Substitute(<q-args>)

function! s:Substitute(patterns)
  if getregtype('s') != ''
    let l:register=getreg('s')
  endif
  normal! qs
  redir => l:replacements
  try
    execute ',$s' . a:patterns . 'gce#'
  catch /^Vim:Interrupt$/
    return
  finally
    normal! q
    let l:transcript=getreg('s')
    if exists('l:register')
      call setreg('s', l:register)
    endif
  endtry
  redir END
  if len(l:replacements) > 0
    " At least one instance of pattern was found.
    let l:last=strpart(l:transcript, len(l:transcript) - 1)
    " Note: type the literal <Esc> (^[) here with <C-v><Esc>:
    if l:last ==# 'l' || l:last ==# 'q' || l:last ==# '^['
      " User bailed.
      return
    endif
  endif

  " Loop around to top of file and continue.
  " Avoid unwanted "Backwards range given, OK to swap (y/n)?" messages.
  if line("''") > 1
    1,''-&&"
  endif
endfunction

Esta função usa alguns hacks para verificar se devemos passar para o topo:

  • Sem agrupamento se o usuário pressionou "l", "q" ou "Esc", qualquer um dos quais indica um desejo de abortar.
  • Detecte aquele toque de tecla final gravando uma macro no registro "s" e inspecionando o último caractere dela.
  • Evite sobrescrever uma macro existente salvando / restaurando o registro "s".
  • Se você já está gravando uma macro ao usar o comando, todas as apostas estão canceladas.
  • Tenta fazer a coisa certa com interrupções.
  • Evita avisos de "intervalo para trás" com uma proteção explícita.
estremecer
fonte
1
Extraí isso em um pequeno plug-in e coloquei no GitHub aqui .
estremeceu em
Acabei de instalar seu plugin, funciona como um charme !! Muito obrigado por fazer isso!
Tropilio
1

Estou atrasado para a festa, mas confiei muito nessas respostas de stackoverflow atrasadas para não fazer isso. Reuni dicas sobre reddit e stackoverflow, e a melhor opção é usar o \%>...cpadrão na pesquisa, que corresponde apenas após o cursor.

Dito isso, ele também confunde o padrão para a próxima etapa de substituição e é difícil de digitar. Para combater esses efeitos, uma função personalizada deve filtrar o padrão de pesquisa posteriormente, redefinindo-o. Ver abaixo.

Eu me contentei com um mapeamento que substitui a próxima ocorrência e pula para a seguinte, e não mais (era meu objetivo de qualquer maneira). Tenho certeza de que, com base nisso, uma substituição global pode ser elaborada. Tenha em mente que ao trabalhar em uma solução que visa algo como :%s/.../.../go padrão abaixo filtra as correspondências em todas as linhas à esquerda da posição do cursor - mas é limpo após a conclusão da única substituição, portanto, perde esse efeito logo após, pula para o próxima partida e, portanto, é capaz de percorrer todas as partidas uma a uma.

fun! g:CleanColFromPattern(prevPattern) return substitute(a:prevPattern, '\V\^\\%>\[0-9]\+c', '', '') endf nmap <F3>n m`:s/\%><C-r>=col(".")-1<CR>c<C-.r>=g:CleanColFromPattern(getreg("/"))<CR>/~/&<CR>:call setreg("/", g:CleanColFromPattern(getreg("/")))<CR>``n

Simlei
fonte
Obrigado, concordo que uma resposta tardia costuma ser útil. Eu pessoalmente não uso mais o Vim há muito tempo (por razões como esta), mas tenho certeza que muitas outras pessoas acharão isso útil.
basteln