2015-07-21 1 views
9

Modification:Annuler VIM: pourquoi le curseur saute-t-il à la mauvaise position en annulant `undojoin`?


Pourquoi le curseur positionné différemment dans les deux exemples suivants :

  1. [POSITION CORRECT CURSOR] Le test suivant produit le changement de substitution de résultat attendu est relié au changement précédent dans le tampon (addition de la ligne 3), la position du curseur est correctement restauré à la seconde ligne dans la mémoire tampon .

    normal ggiline one is full of aaaa 
    set undolevels=10 " splits the change into separate undo blocks 
    
    normal Goline two is full of bbbb 
    set undolevels=10 
    
    normal Goline three is full of cccc 
    set undolevels=10 
    
    undojoin 
    keepjumps %s/aaaa/zzzz/ 
    normal u 
    
  2. [CURSOR position incorrecte] Le test suivant produit un résultat inattendu: le changement de substitution est relié au changement précédent dans le tampon (addition de la ligne 4), la position du curseur est incorrectement restauré à la première ligne dans le tampon (devrait être la ligne 3).

    normal ggiline one is bull of aaaa 
    set undolevels=10 " splits the change into separate undo blocks 
    
    normal Goline two is full of bbbb 
    set undolevels=10 
    
    normal Goline three is full of cccc   
    set undolevels=10 
    
    normal Goline four is full of aaaa's again 
    set undolevels=10 
    
    undojoin 
    keepjumps %s/aaaa/zzzz/ 
    normal u 
    

Original Question

La façon dont mon VIM est mis en place, la sauvegarde d'un tampon à un fichier déclenche une fonction StripTrailingSpaces personnalisés() (ci-joint à la fin de la question):

autocmd BufWritePre,FileWritePre,FileAppendPre,FilterWritePre <buffer> 
     \ :keepjumps call UmkaDK#StripTrailingSpaces(0) 

Après avoir vu Restore the cursor position after undoing text change made by a script, j'ai eu une idée d'exclure les modifications apportées par mes StripTrailingSpaces() fonctionne à partir de l'historique d'annulation en fusionnant l'enregistrement d'annulation créé par la fonction à la fin de la modification précédente dans le tampon. De cette façon, lorsque vous annulez des modifications, il semble que la fonction n'ait pas du tout créé son propre enregistrement d'annulation.

Pour valider mon idée que je l'ai utilisé un exemple simple de test: créer un tampon propre et entrez les commandes suivantes manuellement ou enregistrer le bloc suivant dans un fichier et de la source via:

vim +"source <saved-filename-here>"

normal ggiline one is full of aaaa 
set undolevels=10 " splits the change into separate undo blocks 

normal Goline two is full of bbbb 
set undolevels=10 

normal Goline three is full of cccc 
set undolevels=10 

undojoin 
keepjumps %s/aaaa/zzzz/ 
normal u 

Comme vous pouvez le voir, après avoir annulé la dernière modification dans le tampon, c'est-à-dire en créant la troisième ligne, le curseur est correctement renvoyé à la deuxième ligne du fichier.

Depuis que mon test a fonctionné, j'ai implémenté un undojoin presque identique dans mes StripTrailingSpaces(). Toutefois, lorsque j'annule la dernière modification après l'exécution de la fonction, le curseur est renvoyé à la plus grande modification du fichier. Ceci est souvent un espace dénudé et n'est pas la position de la modification I undojoin -ed à.

Quelqu'un peut-il penser à pourquoi ce serait? Mieux encore, quelqu'un peut-il suggérer une solution?

function! UmkaDK#StripTrailingSpaces(number_of_allowed_spaces) 
    " Match all trailing spaces in a file 
    let l:regex = [ 
       \ '\^\zs\s\{1,\}\$', 
       \ '\S\s\{' . a:number_of_allowed_spaces . '\}\zs\s\{1,\}\$', 
       \ ] 

    " Join trailing spaces regex into a single, non-magic string 
    let l:regex_str = '\V\(' . join(l:regex, '\|') . '\)' 

    " Save current window state 
    let l:[email protected]/ 
    let l:winview = winsaveview() 

    try 
     " Append the comming change onto the end of the previous change 
     " NOTE: Fails if previous change doesn't exist 
     undojoin 
    catch 
    endtry 

    " Substitute all trailing spaces 
    if v:version > 704 || v:version == 704 && has('patch155') 
     execute 'keepjumps keeppatterns %s/' . l:regex_str . '//e' 
    else 
     execute 'keepjumps %s/' . l:regex_str . '//e' 
     call histdel('search', -1) 
    endif 

    " Restore current window state 
    call winrestview(l:winview) 
    let @/=l:last_search 
endfunction 
+0

Désolé, mais quelle est la différence entre ces 75 lignes et ':% s/\ s * $ /'? – steffen

+0

@steffen: Eh bien ... ses 74 lignes et 2866 caractères de plus ... il a aussi des commentaires descriptifs, préserve votre historique de recherche et la dernière chaîne de recherche, ne change pas vos marques ''' ','' .' et ''^', n'ajoute pas un nouvel enregistrement 'jumplist' et' changelist', préserve la position de votre vue et du curseur, et * devrait * créer une expérience d'annulation plus douce. (Bien que le dernier point soit subjectif et la raison pour laquelle cette question est ici.) – UmkaDK

+0

La position du curseur est mémorisée avant d'effectuer des modifications, puis restaurée après avoir annulé les modifications. –

Répondre

3

Cela ressemble vraiment à un bogue avec la commande de substitution pour moi. D'après ce que je peux dire, la commande de substitution prendra sporadiquement l'emplacement de changement pour sauter vers le bloc d'annulation quand il est inclus. Je ne peux pas isoler le motif - parfois il le fera quand la substitution se produira> un certain nombre de fois. D'autres fois, l'emplacement de la substitution semble affecter quand cela se produit. Cela semble être très peu fiable. Je ne pense pas que cela ait vraiment quelque chose à voir avec la commande undojoin car j'ai été capable de reproduire cet effet pour d'autres fonctions qui n'utilisent pas cela. Si vous êtes intéressé, essayez ce qui suit:

function! Test() 
    normal ciwfoo 
    normal ciwbar 
    %s/one/two/ 
endfunction 

Essayez sur certains textes différents avec un nombre différent de « ceux » inclus et placés à des endroits différents. Vous remarquerez que, par la suite, parfois annuler revient à la ligne où la première substitution s'est produite et d'autres fois, il saute à l'endroit où la première commande normale fait son changement.

Je pense que la solution ici pour vous va être de faire quelque chose comme ceci:

undo 
normal ma 
redo 

en haut de votre fonction, puis associez u à quelque chose comme dans votre fonction GPI de sorte qu'après l'annulation, il va revenir à l'endroit où le premier changement réel a eu lieu par opposition à tout ce qui est aléatoire: s forces sur vous. Bien sûr, ça ne peut pas être aussi simple que ça, car vous devrez vous désinvestir une fois que vous aurez fait votre saut, etc., mais ce schéma devrait en général vous donner un moyen de sauvegarder l'emplacement correct et ensuite de revenir en arrière à lui. Bien sûr, vous voudrez probablement faire tout cela avec une variable globale au lieu de marques de piratage, mais vous avez l'idée. Après avoir passé du temps à creuser dans le code source, il semble que le comportement que vous recherchez soit le bogue. Ceci est le morceau de code qui détermine l'endroit où le curseur doit être placé après une annulation:

if (top < newlnum) 
{ 
    /* If the saved cursor is somewhere in this undo block, move it to 
    * the remembered position. Makes "gwap" put the cursor back 
    * where it was. */ 
    lnum = curhead->uh_cursor.lnum; 
    if (lnum >= top && lnum <= top + newsize + 1) 
    { 
    MSG("Remembered Position.\n"); 
    curwin->w_cursor = curhead->uh_cursor; 
    newlnum = curwin->w_cursor.lnum - 1; 
    } 
    else 
    { 
    char msg_buf[1000]; 
    MSG("First change\n"); 
    sprintf(msg_buf, "lnum: %d, top: %d, newsize: %d", lnum, top, newsize); 
    MSG(msg_buf); 
    /* Use the first line that actually changed. Avoids that 
    * undoing auto-formatting puts the cursor in the previous 
    * line. */ 
    for (i = 0; i < newsize && i < oldsize; ++i) 
     if (STRCMP(uep->ue_array[i], ml_get(top + 1 + i)) != 0) 
     break; 
    if (i == newsize && newlnum == MAXLNUM && uep->ue_next == NULL) 
    { 
     newlnum = top; 
     curwin->w_cursor.lnum = newlnum + 1; 
    } 
    else if (i < newsize) 
    { 
     newlnum = top + i; 
     curwin->w_cursor.lnum = newlnum + 1; 
    } 
    } 
} 

Il est plutôt impliqué mais fondamentalement ce que cela fait est de vérifier où se trouvait le curseur lorsque la modification a été apportée et si elle est à l'intérieur du changez le bloc pour l'annulation puis réinitialisez le curseur à cette position pour la commande gw. Sinon, il saute à la ligne la plus modifiée et vous y place. Ce qui se passe avec le substitut, c'est qu'il active cette logique pour chaque ligne qui est substituée et donc si l'une de ces substitutions est dans le bloc d'annulation, elle saute à la position du curseur avant l'annulation (le comportement souhaité). D'autres fois, aucun des changements ne sera dans ce bloc, donc il passera à la ligne la plus haute (probablement ce qu'il devrait faire). Par conséquent, je pense que la réponse à votre question est que votre comportement désiré (faire une modification mais la fusionner avec la modification précédente sauf pour déterminer où placer le curseur lorsque la modification est annulée) n'est pas actuellement supporté par vim.

EDIT: Ce fragment de code se trouve dans undo.c sur la ligne 2711 à l'intérieur de la fonction undoredo.À l'intérieur de u_savecommon, tout est configuré avant que l'annulation soit appelée et c'est là que la position du curseur qui finit par être utilisée pour l'exception de la commande gw est sauvegardée (undo.c ligne 385 et enregistrée sur la ligne 548 lorsqu'elle est appelée sur un tampon synchronisé). La logique de la commande de substitution se trouve dans ex_cmds.c sur la ligne 4268 qui appelle indirectement u_savecommon sur la ligne 5208 (appelle u_savesub qui appelle u_savecommon).

+0

Merci @doliver, il semble que vous ayez passé pas mal de temps à le déboguer. Très appréciée!! Cependant, la solution que vous proposez (à savoir: remapper une action personnalisée) nécessite de modifier l'une des principales fonctionnalités de VIM, et je préfère éviter cela. Aussi, si ce comportement est le résultat d'un bug, ce que vous proposez ne résoudra le problème que pour moi et pas pour la communauté dans son ensemble. Permettez-moi de voir si je peux obtenir #vim (IRC) et vim_dev (liste de diffusion) impliqué dans cela. Peut-être qu'ils pourront nous aider. – UmkaDK

+0

Cette question est maintenant postée sur la liste de diffusion vim_dev: https://goo.gl/HpW4NX – UmkaDK

+1

Oui, je pense idéalement que cela serait fixé à l'intérieur du noyau. Je ne pense pas que vous aurez la priorité absolue avec un problème comme celui-ci. Si vous êtes à l'aise avec C je ne serais pas surpris si cela pouvait être réparé assez facilement dans le noyau. En dehors de cela ou de remappage de la touche u, la meilleure solution consiste probablement à ne pas faire le undojoin qui nécessiterait une pression supplémentaire mais conserver cette position. J'ai jeté un coup d'oeil dans le noyau de vim avant que je puisse jeter un coup d'oeil à un certain point aujourd'hui et voir si cela semble être une solution facile. – doliver