Posso ser notificado quando estou desfazendo alterações do undofile?

15

Estou usando o recurso undofile no Vim há um tempo. É um recurso muito bom.

No entanto, um aborrecimento é que é muito fácil desfazer acidentalmente as alterações que fiz na última vez em que abri o arquivo; que pode ser de 2 minutos atrás, uma hora atrás, na semana passada ou um mês atrás.

Por exemplo, digamos que eu abro um arquivo, faça algumas alterações, desative e altere alguns outros arquivos e descubra que minhas alterações não foram necessárias ou talvez sejam apenas algumas instruções de depuração temporárias.

Anteriormente, eu podia apenas segurar a utecla até Vim dizer "Já está na mudança mais antiga" :wq, e estar pronto. Mas agora tenho que ter muito cuidado para não desfazer as alterações que fiz da última vez que abri o arquivo. Não há uma maneira óbvia de ver quando você está fazendo isso.

Existe alguma maneira de tornar isso mais explícito? Por exemplo, mostrando em algum lugar, emitindo um aviso ou até solicitando uma confirmação.

Martin Tournoij
fonte
Esta é uma pergunta que eu me pergunto desde a inauguração do recurso undofile, e que pretendo perguntar desde a inauguração deste site! Esperando que haja uma boa resposta. Acho que também aceitaria uma resposta que desse um comando que revertesse o arquivo para o estado em que estava desde a última vez que você o abriu . (Ou melhor, pulou de volta para esse estado de desfazer.) #
232 Rich Rich

Respostas:

8

Eu tive esse problema exato. Aqui está o que eu adicionei ao meu vimrc para corrigi-lo:

" 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

Observe que você não define a undofileopção; você precisa remover o seu vimrc, se o tiver.

Desfazer agora funciona como "normal". Mas nós não escrever manualmente para o arquivo de desfazer com o wundocomando BufWritePost.

A ReadUndo()função lê manualmente o arquivo de desfazer rundoe define a undofileopção. Agora podemos usar upara voltar à história. Eu mapeei isso para <Leader>u.

Provavelmente isso poderia ser melhor. Ele substitui as informações de desfazer anteriores, para que você não possa voltar para a hora anterior anterior em que editou o arquivo. Mas eu não precisava disso.

superjer
fonte
Observe que notei um problema com isso: você salva apenas o conteúdo da sessão atual no arquivo de desfazer. Isso é diferente do comportamento padrão, onde o conteúdo da sessão anterior + todas as informações que já estão no arquivo de desfazer são salvas ... A maneira de corrigir isso seria ler o arquivo de desfazer, mesclar as alterações de desfazer e depois escrever o arquivo de desfazer ... Mas isso não parece possível ...: - /
Martin Tournoij
@Carpetsmoker Eu concordo. Seria bom fazer isso se fosse possível. Por acaso, isso funcionou bem o suficiente para mim por um longo tempo. YMMV.
Super8 de
5

Oooh, ooh, finalmente a chance de mostrar esse comando bacana!

Vim pode "voltar no tempo". Tem um :earliercomando ...

                                                        :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.                                

... que pode reverter o arquivo para um estado anterior. Isso pode ser usado de várias maneiras.

  • Se você puder fazer uma estimativa aproximada de quanto tempo você levou para fazer essas alterações, poderá fornecer um tempo para esse comando. Ou, por exemplo, se você souber que não alterou o arquivo (exceto as alterações que deseja desfazer) no dia anterior, poderá usar

    :earlier 1d
    
  • Se você tiver gravado (salvo) o arquivo apenas uma vez desde as alterações que deseja desfazer ou se não tiver salvo o arquivo, poderá usar

    :earlier 1f
    

    Conforme descrito no :helptexto, isso reverterá para a versão escrita anteriormente, se você acabou de escrever o arquivo, ou para a última vez que ele foi salvo, se você não tiver nenhuma alteração salva.

Isso não responde à sua pergunta com precisão, mas parece um pouco de um problema XY.

Maçaneta
fonte
1
parece um pouco de um problema XY : Por quê? Estou me desfazendo ue, acidentalmente, passo além das alterações que fiz agora ... Não sei ao certo qual poderia ser o "problema X" original?
Martin Tournoij 23/02
1
Eu agora :earliera propósito, mas ainda preciso adivinhar ; assim como eu preciso adivinhar ao usar o u... Nos casos, provavelmente um pouco melhor, mas eu preferiria algo mais explícito (se possível).
Martin Tournoij 23/02
1
@Carpetsmoker "X" é "Quero desfazer apenas essas alterações que fiz mais recentemente". "Y" é "Como posso desfazer alterações e ignorar o desfile?" Você ainda tem que adivinhar, mas disse na sua pergunta que as últimas alterações feitas foram na semana passada ou anterior, para que você pudesse fazer algo parecido :ea 5d. Você também pode usar a :ea 1fabordagem. De qualquer forma, é muito menos granular.
Maçaneta 23/02
"X" e "Y" parecem apenas uma reformulação do mesmo problema para mim? Eu mencionei "semanas" na minha pergunta, mas também pode demorar horas ou minutos (modificado isso) ... Essa não é uma resposta ruim , a propósito, eu estava (e estou) apenas esperando que haja algo melhor ...
Martin Tournoij
Estou com @Carpetsmoker neste. Eu já sabia sobre: ​​mais cedo há algum tempo, mas ainda assim, o undofile foi desativado exatamente pelo motivo descrito na pergunta.
Rich
4

Atualização 28-06-2015 : Corrigi um pequeno bug e o liberei como um plug-in . O código do plugin é um pouco melhor, pois avisa novamente depois de mover o cursor; Eu recomendo que você use o plugin.



A resposta do superjer funciona muito bem, mas tem o efeito colateral infeliz de que você só pode desfazer alterações da última sessão do Vim, e nem todas as sessões anteriores do Vim.

Isso ocorre porque wundosubstitui o arquivo de desfazer; não é mesclado. Tanto quanto eu sei, não há como corrigir isso.

Portanto, aqui está minha solução alternativa: ela exibirá uma grande mensagem de aviso em vermelho quando você estiver desfazendo alterações no arquivo de desfazer.

Isso é semelhante à resposta de Ingo Karkat , mas não requer um plug-in externo e possui algumas diferenças sutis (exibe aviso em vez de bipe, não é necessário pressionar uduas vezes).

Nota que este apenas modifica o ue <C-r>se liga, e não o U, :undoe :redocomandos.

" 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
fonte
3

Eu tenho o seguinte, que para e emite um bipe quando desfazer atinge o conteúdo persistente do buffer.

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>

Isso se integra ao plugin repeat.vim; requer meu plugin ingo-library .

Ingo Karkat
fonte
Você está usando "persistente" para significar "o estado em que o arquivo estava quando o buffer foi carregado"? Eu normalmente esperaria que "persistisse" significasse o estado do arquivo atualmente salvo no disco.
Rich
@ Rich: Não, temos a mesma definição. Meus mapeamentos não fazem exatamente o que a pergunta pede, mas eu ainda a achei muito útil.
Ingo Karkat
2

Acho que também aceitaria uma resposta que desse um comando que revertesse o arquivo para o estado em que estava desde a última vez que você o abriu. (Ou melhor, retornou a esse estado de desfazer.)

> Rico

Eu realmente gostei da idéia citada, então tomei o :Revertcomando. Espero que você ache relevante para sua pergunta.

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)

As funções auxiliares Get_seqe Real_seq, com base em não- árvore , são necessárias porque undotree()['seq_cur']às vezes não é suficiente para identificar a posição atual na árvore de desfazer. Você pode ler mais sobre isso aqui .

dsfdsfd
fonte
isso parece uma boa solução. Mas você tem um erro no seu au. o vimrc lá é o culpado. Simplesmente deixe de fora
Naumann