Qual é a diferença entre os átomos '\ zs' e '\ @ <=' no regex do Vim?

11

É o que recebo da documentação: \zs"inicia a parte destacada" após corresponder ao regex anterior e \@<="inicia a parte destacada" após corresponder ao átomo anterior . Mas eu não entendo exatamente as sutilezas disso, então alguém pode explicar como elas diferem um pouco mais em profundidade?

Foi isso que me deixou curioso: se eu correr

/\_s\zsnnoremap

ou seja, selecione nnoremapprecedido por um espaço ou um início de linha (ou seja, a nova linha da linha anterior e, portanto, a \_anterior s) e depois corra gnpara entrar no Modo Visual e selecione visualmente a próxima correspondência, por algum motivo, apenas a primeira coluna (ou seja, o primeiro nem nnoremap) é selecionado - apesar do fato de que toda a nnoremappalavra é realçada com :hlsearchligado.

No entanto, se eu executar a pesquisa

/\_s\@<=nnoremap

e tente gn, o todo nnoremapestá selecionado corretamente. O que poderia estar acontecendo aqui? Eu (ouso dizer) descobri algum bug obscuro?

Luke Davis
fonte
Acho que está, :h patternsmas minha memória sugere que os regexs são compostos de átomos, se isso ajudar a explicar a diferença.
D. Ben Knoble

Respostas:

15

Parece que você realmente encontrou um bug obscuro. Eu implementei o gntextobject em 2012 para o Vim 7.3. Basicamente, funciona da seguinte maneira:

1) Ele pesquisa para trás a última correspondência da expressão regular atual.

2) Procura a próxima correspondência da expressão regular atual.

Isso deve deixar claro que o cursor estará no início da próxima partida, mesmo se já estivesse lá no início de 1). Finalmente

3) procura o final da expressão regular atual. e coloca o cursor lá.

Agora, o que acontece aqui é que a busca pelo final da partida atual se aproxima e volta para o final da partida anterior (porque wrapscanjá está definida, depois de ser desabilitada para 1). Em seguida, define o marcador Visual para a área desde o início (final do ponto 2) e a área movida para o próximo item de pesquisa 3).

Examinarei mais de perto o problema e provavelmente enviará um patch para o Vim mais tarde.

[Atualização 22.05.2018] Eu escrevi e enviei um patch para corrigir esse comportamento.

[Atualização2 22.05.2018] E o patch foi mesclado como nível de patch 8.1.0018

[Atualização 22.10.2019] No patch 8.1.629 do Vim, a terceira etapa não é mais executada. Em vez disso, o Vim agora pode determinar o final da partida ao encontrar o início da partida (Etapa 2)

Christian Brabandt
fonte
8

Christian abordou completamente a questão do comportamento de buggy de gn, mas ainda existem diferenças fundamentais entre \zse \@<=. O maior ser \@<=modifica um átomo anterior, enquanto \zsé um átomo em si.

Considerar:

Xnnoremap

\%1cX\zsnnoremap     (regex 1)
\%1cX\@<=nnoremap    (regex 2)
\%2cX\@<=nnoremap    (regex 3)

O Regex 1 corresponde, pois \%1ccorresponde à coluna 1 e existe um X lá. \zsapenas faz com que a partida seja reiniciada em uma posição após o X.

O Regex 2, no entanto, não corresponde, porque, embora \%1ccorresponda à primeira coluna, X\@<=tem largura zero (conforme mencionado na documentação) e nnoremapinicia na coluna 2. Não há nada para compensar a diferença de posição entre as colunas 1 e 2.

O Regex 3 corresponde desde o nnoremapinício na coluna 2.

Massa
fonte
1
Não acho que a regex 2 falhe porque não há nada para compensar a diferença de posição entre as colunas 1 e 2. Se esse fosse o problema, a remoção nnoremapda regex produziria uma correspondência; mas o regex ainda falha mesmo sem. Eu acho que falha porque \%1cX\@<=expressa uma posição que não pode existir. \%1ccorresponde à posição na coluna 1 e X\@<=solicita que um personagem Xcorresponda antes disso. Mas não pode haver nenhum caractere antes da primeira coluna. É por isso que, mesmo se você substituir Xpor um ponto (qualquer caractere), a regex \%1c.\@<=ainda falhará.
User938271
4

\zsaplica-se a toda a expressão regular e define o próximo caractere como o primeiro caractere de toda a correspondência. Qualquer coisa anterior ao \zsnão será incluída como parte do texto correspondente.

\@<=, por outro lado, afeta apenas os átomos diretamente ao seu redor, permitindo especificar que o próximo átomo corresponderá apenas se seguir o átomo anterior. Então, por exemplo, a expressão regular:

\vbar.*(foo)@<=bar

Corresponderá todo o texto entre duas instâncias de bar(incluindo as próprias instâncias), mas apenas se a segunda for precedida por foo. ou seja, corresponderá:

barbazfoobar

mas não:

barbazbazbar

Como \@<=é localizado dessa maneira, você pode até usar \@<=várias vezes em uma única expressão:

\vbar.*(foo)@<=bar.*(foo)@<=bar

O seguinte corresponderá a três instâncias de bar, mas somente se as duas primeiras forem precedidas por foo.

ou seja, dado o texto:

barfoobarbazfoobar
barfoobarbazbazbar
barbazbarbazfoobar

Ele corresponderá apenas à primeira linha.

Rico
fonte
Mas você pode trocar o primeiro lookbehind com \zs, ou seja, isso também deve funcionar: \vfoo\zsbar.*(foo)@<=bar.
Karl Yngve Lervåg
@ KarlYngveLervåg Bom ponto. Eu editei para tornar mais clara a distinção e usar exemplos onde \zsnão podem ser substituídos.
Rich
Então, pelo que entendi, \zse \zepode ser substituído por olhar em torno de padrões regex, e eles são mais poderosos, certo? Causa mais poderosa: eles podem ser usados ​​mais de uma vez e podem ser agrupados \(\). E também porque eles funcionam como o perl olha em volta do regex. Algo errado?
Klaus
1
@klaus Isso parece certo para mim (embora eu não seja especialista). Observe que você deve usar \zs/ \zequando puder, porque é mais rápido do que olhar em volta.
29319 Rich
Entendido. E \zse \zesão, obviamente, mais intuitiva. Obrigado pelas explicações.
Klaus