Puis-je être averti lorsque j'annule les modifications apportées au fichier d'annulation?

15

J'utilise la fonction undofile dans Vim depuis un certain temps maintenant. C'est une fonctionnalité très intéressante.

Cependant, une gêne est qu'il est très facile d'annuler accidentellement les modifications que j'ai faites la dernière fois que j'ai ouvert le fichier; qui peut être il y a 2 minutes, il y a une heure, la semaine dernière ou il y a un mois.

Par exemple, supposons que j'ouvre un fichier, que j'apporte quelques modifications, que je quitte et modifie d'autres fichiers et découvre que mes modifications n'étaient pas nécessaires, ou peut-être n'étaient-ce que quelques instructions de débogage temporaires.

Auparavant, je pouvais simplement garder la uclé jusqu'à ce que Vim dise "Déjà au plus vieux changement" :wq, et c'est fait. Mais maintenant, je dois faire très attention à ne pas annuler les modifications que j'ai apportées la dernière fois que j'ai ouvert le fichier. Il n'y a aucun moyen évident de voir quand vous faites cela.

Existe-t-il un moyen de rendre cela plus explicite? Par exemple, en le montrant quelque part, en émettant un avertissement ou même en demandant une confirmation.

Martin Tournoij
la source
C'est une question que je me suis posée depuis le dévoilement de la fonctionnalité undofile, et que je voulais poser depuis le dévoilement de ce site! En espérant qu'il y ait une bonne réponse. Je pense que j'accepterais également une réponse qui donne une commande qui ramène le fichier à l'état dans lequel il se trouvait depuis la dernière fois que vous l'avez ouvert . (Ou mieux, est revenu à cet état d'annulation.)
Rich

Réponses:

8

J'ai eu exactement ce problème. Voici ce que j'ai ajouté à mon vimrc pour le corriger pour moi:

" Always write undo history, but only read it on demand
" use <leader>u to load old undo info
" modified from example in :help undo-persistence

if has('persistent_undo')
  set undodir=~/.vim/undo " location to store undofiles
  nnoremap <leader>u :call ReadUndo()<CR>
  au BufWritePost * call WriteUndo()
endif

func! ReadUndo()
  let undofile = undofile(expand('%'))
  if filereadable(undofile)
    let undofile = escape(undofile,'% ')
    exec "rundo " . undofile
  endif
endfunc

func! WriteUndo()
  let undofile = escape(undofile(expand('%')),'% ')
  exec "wundo " . undofile
endfunc

Notez que vous ne définissez pas l' undofileoption; vous devez retirer votre vimrc si vous l'avez.

Annuler fonctionne désormais comme "normal". Mais nous n'écrire manuellement le fichier undo avec la commande en .wundoBufWritePost

La ReadUndo()fonction lit manuellement le fichier d'annulation avec rundoet définit l' undofileoption. Maintenant, nous pouvons utiliser upour remonter plus loin dans l'histoire. J'ai mappé ça à <Leader>u.

Cela pourrait probablement être mieux. Il écrase les informations d'annulation précédentes, vous ne pouvez donc pas revenir à la précédente fois où vous avez modifié le fichier. Mais je n'en ai pas eu besoin moi-même.

superjer
la source
Remarque J'ai remarqué un problème avec ceci: vous enregistrez uniquement le contenu de la session en cours dans le fichier d'annulation. Ceci est différent du comportement par défaut, où le contenu de la session précédente + toutes les informations déjà dans le fichier d'annulation sont enregistrées ... La façon de résoudre ce problème serait de lire le fichier d'annulation, de fusionner les modifications d'annulation, puis d'écrire le fichier d'annulation ... Mais cela ne semble pas possible ...: - /
Martin Tournoij
@Carpetsmoker Je suis d'accord. Ce serait bien de le faire si c'était possible. En fait, cela a fonctionné assez bien pour moi pendant longtemps. YMMV.
superjer
5

Oooh, ooh, enfin une chance de montrer cette commande astucieuse!

Vim peut "remonter le temps". Il a une :earliercommande ...

                                                        :ea :earlier            
:earlier {count}        Go to older text state {count} times.                   
:earlier {N}s           Go to older text state about {N} seconds before.        
:earlier {N}m           Go to older text state about {N} minutes before.        
:earlier {N}h           Go to older text state about {N} hours before.          
:earlier {N}d           Go to older text state about {N} days before.           

:earlier {N}f           Go to older text state {N} file writes before.          
                        When changes were made since the last write             
                        ":earlier 1f" will revert the text to the state when    
                        it was written.  Otherwise it will go to the write      
                        before that.                                            
                        When at the state of the first file write, or when      
                        the file was not written, ":earlier 1f" will go to      
                        before the first change.                                

... qui peut ramener le fichier à un état précédent. Cela peut être utilisé de plusieurs façons.

  • Si vous pouvez faire une estimation approximative du temps qu'il vous a fallu pour effectuer ces modifications, vous pouvez alimenter une heure dans cette commande. Ou, par exemple, si vous savez que vous n'avez pas modifié le fichier (à l'exception des modifications que vous souhaitez annuler) au cours de la dernière journée, vous pouvez utiliser

    :earlier 1d
    
  • Si vous n'avez écrit (enregistré) le fichier qu'une seule fois depuis les modifications que vous souhaitez annuler ou si vous ne l'avez pas enregistré du tout, vous pouvez utiliser

    :earlier 1f
    

    Comme décrit dans le :helptexte, cela reviendra à la version précédemment écrite si vous venez d'écrire le fichier, ou reviendra à la dernière fois qu'il a été enregistré si vous n'avez aucune modification enregistrée.

Cela ne répond pas précisément à votre question, mais cela ressemble un peu à un problème XY.

Poignée de porte
la source
1
cela ressemble un peu à un problème XY : pourquoi? Je suis en train de défaire u, et je dépasse accidentellement les modifications que j'ai apportées en ce moment ... Je ne sais pas quel pourrait être le "problème X" d'origine ici?
Martin Tournoij
1
Je suis maintenant sur :earlierle chemin, mais je dois encore deviner ; tout comme je dois deviner lors de l'utilisation du u... Dans les cas c'est probablement un peu mieux, mais je préférerais quelque chose de plus explicite (si possible).
Martin Tournoij
1
@Carpetsmoker "X" signifie "Je souhaite annuler uniquement les modifications que j'ai apportées le plus récemment". "Y" est "Comment puis-je annuler les modifications et ignorer l'annulation du fichier?" Vous devez en quelque sorte encore deviner, mais vous avez dit dans votre question que les derniers changements que vous avez apportés étaient la semaine dernière ou plus tôt, donc vous pourriez simplement faire quelque chose comme :ea 5d. Vous pouvez également utiliser l' :ea 1fapproche. En tout cas, c'est beaucoup moins granuleux.
Poignée de porte
"X" et "Y" me semblent juste reformuler le même problème? J'ai fait mention de "semaines" dans ma question, mais ça pourrait aussi être des heures, ou des minutes (modifié ça) ... Ce n'est pas une mauvaise réponse en tant que telle d'ailleurs, j'étais (et je suis) en espérant juste qu'il y a quelque chose de mieux ...
Martin Tournoij
Je suis avec @Carpetsmoker sur celui-ci. Je connais: plus tôt depuis un certain temps, mais j'ai toujours désactivé le fichier pour la raison décrite dans la question.
Rich
4

Mise à jour 2015-06-28 : j'ai corrigé un petit bogue et l'ai publié en tant que plugin . Le code du plugin est légèrement meilleur, en ce sens qu'il avertit à nouveau après avoir déplacé le curseur; Je vous recommande d'utiliser le plugin.



La réponse de superjer fonctionne très bien, mais a l'effet secondaire malheureux que vous ne pouvez annuler que les modifications de la dernière session Vim, et pas toutes les sessions Vim précédentes.

En effet, il wundoécrase le fichier d'annulation; ce n'est pas fusionné. Pour autant que je sache, il n'y a aucun moyen de résoudre ce problème.

Voici donc ma solution alternative, elle affichera un gros message d'avertissement rouge lorsque vous annulez les modifications du fichier d'annulation.

Ceci est similaire à la réponse d'Ingo Karkat , mais il ne nécessite pas de plugin externe et présente quelques différences subtiles (affiche un avertissement au lieu d'un bip, ne vous oblige pas à appuyer udeux fois).

Notez que ce seulement modifie la uet <C-r>se fixe, et non la U, :undoet les :redocommandes.

" Use the undo file
set undofile

" When loading a file, store the curent undo sequence
augroup undo
    autocmd!
    autocmd BufReadPost,BufCreate,BufNewFile * let b:undo_saved = undotree()['seq_cur'] | let b:undo_warned = 0
augroup end 

" Remap the keys
nnoremap u :call Undo()<Cr>u
nnoremap <C-r> <C-r>:call Redo()<Cr>


fun! Undo()
    " Don't do anything if we can't modify the buffer or there's no filename
    if !&l:modifiable || expand('%') == '' | return | endif

    " Warn if the current undo sequence is lower (older) than whatever it was
    " when opening the file
    if !b:undo_warned && undotree()['seq_cur'] <= b:undo_saved
        let b:undo_warned = 1
        echohl ErrorMsg | echo 'WARNING! Using undofile!' | echohl None
        sleep 1
    endif
endfun

fun! Redo()
    " Don't do anything if we can't modify the buffer or there's no filename
    if !&l:modifiable || expand('%') == '' | return | endif

    " Reset the warning flag
    if &l:modifiable && b:undo_warned && undotree()['seq_cur'] >= b:undo_saved
        let b:undo_warned = 0
    endif
endfun
Martin Tournoij
la source
3

J'ai ce qui suit, qui s'arrête et émet un bip lorsque l'annulation atteint le contenu du tampon persistant.

runtime autoload/repeat.vim " Must load the plugin now so that the plugin's mappings can be overridden.
let s:undoPosition = []
function! s:StopAtSavedPosition( action )
    " Buffers of type "nofile" and "nowrite" never are 'modified', so only do
    " the check for normal buffers representing files. (Otherwise, the warning
    " annoyingly happens on every undo.)
    if ingo#buffer#IsPersisted() && ! &l:modified && s:undoPosition != ingo#record#PositionAndLocation(1)
        " We've reached the undo position where the buffer contents correspond
        " to the persisted file. Stop and beep, and only continue when undo is
        " pressed again at the same position.
        call ingo#msg#WarningMsg(a:action . ' reached saved buffer state')
        execute "normal! \<C-\>\<C-n>\<Esc>" | " Beep.

        let s:undoPosition = ingo#record#PositionAndLocation(1)
        return 1
    else
        let s:undoPosition = []
        return 0
    endif
endfunction
nnoremap <silent> u     :<C-U>if ! &l:modifiable<Bar>execute 'normal! u'<Bar>elseif ! <SID>StopAtSavedPosition('Undo')<Bar>call repeat#wrap('u',v:count)<Bar>endif<CR>
nnoremap <silent> <C-R> :<C-U>if ! &l:modifiable<Bar>execute "normal! \<lt>C-R>"<Bar>elseif ! <SID>StopAtSavedPosition('Redo')<Bar>call repeat#wrap("\<Lt>C-R>",v:count)<Bar>endif<CR>

Cela s'intègre au plugin repeat.vim; il nécessite mon plugin ingo-library .

Ingo Karkat
la source
Utilisez-vous "persisté" pour signifier "l'état dans lequel se trouvait le fichier lors du chargement du tampon"? Je m'attendrais généralement à ce que «persisté» signifie l'état du fichier actuellement enregistré sur le disque.
Rich
@Rich: Non, nous avons la même définition. Mes mappages ne font pas exactement ce que demande la question, mais je l'ai quand même trouvée très utile.
Ingo Karkat
2

Je pense que j'accepterais également une réponse qui donne une commande qui ramène le fichier à l'état dans lequel il se trouvait depuis la dernière fois que vous l'avez ouvert. (Ou mieux, est revenu à cet état d'annulation.)

> Riche

J'ai vraiment aimé l'idée citée alors j'ai fait :Revertcommande. J'espère que vous le trouverez pertinent pour votre question.

function! s:Real_seq(inner, outer) abort
  let node = a:outer
  for i in a:inner
    if has_key(i, 'alt')
      call s:Real_seq(i.alt, deepcopy(node))
    endif
    if has_key(i, 'curhead')
      return {'seq': node.seq}
    endif
    let node.seq  = i.seq
  endfor
endfunction

function! s:Get_seq(tree) abort
  let query = s:Real_seq(a:tree.entries, {'seq': 0})
  if (type(query) == 4)
    return query.seq
  else
    return undotree()['seq_cur']
  endif
endfunction

au BufReadPost,BufNewFile * if !exists('b:undofile_start') 
      \ | let b:undofile_start = s:Get_seq(undotree()) | endif

command! -bar Revert execute "undo " . get(b:, 'undofile_start', 0)

Les fonctions d'assistance Get_seqet Real_seq, basées sur undotree , sont nécessaires car undotree()['seq_cur']elles ne sont parfois pas suffisantes pour localiser la position actuelle dans l'arbre d'annulation. Vous pouvez en lire plus ici .

dsfdsfd
la source
cela ressemble à une bonne solution. Mais vous avez une erreur dans votre au. le vimrc là-dedans est le coupable. Laissez-le simplement de côté
Naumann