2015-07-21 15 views
9

modifiche:VIM annulla: Perché il cursore salta nella posizione sbagliata quando si annulla il `annullamento '?


Perché il cursore posizionato diverso nei due seguenti esempi :

  1. [CORRETTO POSIZIONE CURSORE] Il seguente test produce la variazione sostituzione risultato atteso è unito alla modifica precedente nel buffer (aggiunta di linea 3), la posizione del cursore è correttamente ripristinato alla seconda linea nel buffer .

    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. [INESATTA POSIZIONE CURSORE] Il seguente test produce un risultato inatteso: modifica sostituzione è unito alla modifica precedente nel buffer (aggiunta di linea 4), la posizione del cursore è erroneamente ripristinato alla prima linea nel buffer (dovrebbe essere la riga 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 
    

domanda iniziale

Il modo in cui la mia VIM è impostato, il salvataggio di un buffer per un file innesca una StripTrailingSpaces personalizzati() la funzione (in allegato alla fine della questione):

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

Dopo aver visto Restore the cursor position after undoing text change made by a script, mi è venuta un'idea per escludere le modifiche apportate dai miei StripTrailingSpaces() funzione dalla cronologia degli annullamenti unendo il record di annullamento creato dalla funzione alla fine della modifica precedente nel buffer.

In questo modo, quando si annullano le modifiche, sembra che la funzione non abbia creato il proprio record di annullamento.

Per convalidare la mia idea ho usato un semplice caso di test: creare un buffer pulito e immettere i seguenti comandi manualmente, o salvare il seguente blocco come un file e la fonte tramite:

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 

Come si può vedere, dopo aver annullato l'ultima modifica nel buffer, che sta creando la terza riga, il cursore viene correttamente restituito alla seconda riga del file.

Poiché il mio test ha funzionato, ho implementato un identico undojoin nel mio StripTrailingSpaces(). Tuttavia, quando annullo l'ultima modifica dopo l'esecuzione della funzione, il cursore viene riportato all'inizio della maggior parte delle modifiche nel file. Questo è spesso uno spazio vuoto ed è non la posizione della modifica I undojoin -ed a.

Qualcuno può pensare perché questo sarebbe? Meglio ancora, qualcuno può suggerire una correzione?

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

Ci dispiace, ma qual è la differenza tra queste 75 righe e ':% s/\ s * $ /'? – steffen

+0

@steffen: Beh ... le sue 74 righe e 2866 caratteri più a lungo ... ha anche commenti descrittivi, conserva la cronologia delle ricerche e l'ultima stringa di ricerca, non cambia i segni "" "," "." E "^", non aggiunge un nuovo record 'jumplist' e' changelist', conserva la vista e la posizione del cursore e * dovrebbe * creare un'esperienza di annullamento del fumo. (Anche se l'ultimo punto è soggettivo ed è la ragione per cui questa domanda è qui.) – UmkaDK

+0

La posizione del cursore viene ricordata prima di apportare modifiche e quindi ripristinata dopo aver annullato le modifiche. –

risposta

3

Questo sicuramente sembra un insetto con il comando sostitutivo. Da quello che posso dire il comando sostitutivo assumerà sporadicamente la posizione di modifica per saltare al blocco di annullamento quando è incluso. Non riesco a isolare il modello - a volte lo farà quando la sostituzione avviene> un certo numero di volte. Altre volte, la posizione della sostituzione sembra influenzare quando ciò accade. Sembra essere molto inaffidabile. Non penso che in realtà abbia nulla a che fare con il comando di annullamento del gioco, come sono stato in grado di riprodurre questo effetto per altre funzioni che non ne fanno uso. Se siete interessati, provare quanto segue:

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

provarlo su alcuni testi diversi con un diverso numero di "uno" inclusi e collocati in luoghi diversi. Noterai che in seguito a volte l'annullamento salterà alla linea in cui si è verificata la prima sostituzione e altre volte salterà al punto in cui il primo comando normale apporta la propria modifica.

Penso che la soluzione qui per voi sta per essere quello di fare qualcosa di simile:

undo 
normal ma 
redo 

in cima alla vostra funzione e quindi associare u a qualcosa come u'a nella funzione in modo che dopo l'annullamento tornerà al punto in cui si è verificato il primo cambiamento effettivo rispetto a qualsiasi casualità: le forze su di te. Ovviamente, non può essere così semplice perché dovrai smetterla quando hai fatto il tuo salto, ecc. Ecc. Ma questo schema in generale dovrebbe darti un modo per salvare la posizione corretta e poi tornare indietro ad esso. Certo, probabilmente vorrai fare tutto questo con qualche variabile globale invece di dirottare i marchi ma hai un'idea.

EDIT: Dopo aver trascorso qualche tempo a scavare attraverso il codice sorgente, in realtà sembra che il comportamento che siete dopo è il bug. Questo è il pezzo di codice che determina dove il cursore deve essere inserito dopo un annullamento:

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; 
    } 
    } 
} 

È piuttosto coinvolto ma fondamentalmente ciò che fa è controllare dove il cursore era quando è stata apportata la modifica e quindi se è all'interno cambia blocco per annullare quindi resetta il cursore su quella posizione per il comando gw. Altrimenti, salta alla riga più in alto e ti mette lì. Quello che succede con il sostituto è che attiva questa logica per ogni riga che viene sostituita e quindi se una di quelle sostituzioni si trova nel blocco di annullamento, salta alla posizione del cursore prima dell'annullamento (il comportamento desiderato). Altre volte, nessuna delle modifiche sarà in quel blocco, quindi salterà alla riga modificata più in alto (probabilmente cosa dovrebbe fare). Pertanto, penso che la risposta alla tua domanda sia che il tuo comportamento desiderato (apportare una modifica ma unirlo alla modifica precedente eccetto per determinare dove posizionare il cursore quando la modifica è annullata) non è attualmente supportato da vim.

MODIFICA: Questo particolare pezzo di codice risiede in undo.c sulla riga 2711 all'interno della funzione undoredo.All'interno di u_savecommon è dove viene impostata l'intera operazione prima che venga effettivamente chiamato l'annullamento ed è qui che la posizione del cursore che finisce per essere utilizzata per l'eccezione del comando gw viene salvata (riga annullata 385 e salvata sulla linea 548 quando viene richiamata su un buffer sincronizzato). La logica per il comando di sostituzione si trova in ex_cmds.c sulla riga 4268 che chiama u_savecommon indirettamente sulla riga 5208 (chiama u_savesub che chiama u_savecommon).

+0

Grazie a @doliver, sembra che tu abbia impiegato un bel po 'di tempo per eseguire il debug di questo. Molto apprezzato!! Tuttavia, la soluzione che stai proponendo (es .: rimappare 'u' in un'azione personalizzata) richiede la modifica di una delle funzionalità principali di VIM, e preferirei evitarlo. Inoltre, se questo comportamento è il risultato di un bug, ciò che stai proponendo risolverebbe il problema solo per me e non per la comunità nel suo complesso. Fammi vedere se riesco a ottenere #vim (IRC) e vim_dev (mailing list) coinvolti in questo. Forse saranno in grado di aiutarci. – UmkaDK

+0

Questa domanda è ora incrociata sulla mailing list vim_dev: https://goo.gl/HpW4NX – UmkaDK

+1

Sì, penso che idealmente questo sarebbe stato risolto all'interno del core. Non penso che avrai la massima priorità con un problema come questo però. Se ti trovi bene con C non sarei sorpreso se questo potesse essere risolto abbastanza facilmente nel nucleo. A prescindere dal fare ciò o rimappare la chiave u probabilmente la soluzione migliore è semplicemente non eseguire il annullamento del join che richiederebbe la pressione extra del tasto, ma preservare quella posizione. Ho fatto un po 'di giri in vim core prima di poter dare un'occhiata ad un certo punto oggi e vedere se sembra una soluzione facile. – doliver

Problemi correlati