¿Puedo recibir una notificación cuando estoy deshaciendo cambios de unofofile?

15

He estado usando la función no archivada en Vim por un tiempo. Es una característica muy bonita.

Sin embargo, una molestia es que es muy fácil deshacer accidentalmente los cambios que hice la última vez que abrí el archivo; que puede ser hace 2 minutos, hace una hora, la semana pasada o hace un mes.

Por ejemplo, supongamos que abro un archivo, hago algunos cambios, apago y cambio algunos otros archivos, y descubro que mis cambios no fueron necesarios, o tal vez fueron solo unas pocas declaraciones de depuración temporales.

Anteriormente, podía mantener presionada la utecla hasta que Vim dijera "Ya en el cambio más antiguo" :wq, y listo. Pero ahora tengo que tener mucho cuidado de no deshacer los cambios que hice la última vez que abrí el archivo. No hay una forma obvia de ver cuándo estás haciendo esto.

¿Hay alguna manera de hacer esto más explícito? Por ejemplo, mostrándolo en algún lugar, emitiendo una advertencia o incluso pidiendo una confirmación.

Martin Tournoij
fuente
Esta es una pregunta que me he preguntado desde la presentación de la función no archivada, y una que he tenido la intención de preguntar desde la presentación de este sitio. Esperando que haya una buena respuesta. Creo que también aceptaría una respuesta que diera un comando que revierte el archivo al estado en que se encontraba desde la última vez que lo abrió . (O mejor, volví a ese estado de deshacer.)
Rico

Respuestas:

8

Yo tuve el mismo problema. Esto es lo que agregué a mi vimrc para solucionarlo:

" 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

Tenga en cuenta que no establece la undofileopción; debe eliminarlo de su vimrc si lo tiene.

Deshacer ahora funciona como "normal". Pero nosotros hacemos escribir manualmente en el archivo de deshacer con el wundocomando en BufWritePost.

La ReadUndo()función lee manualmente el archivo deshacer con rundoy establece la undofileopción. Ahora podemos usar upara retroceder más en la historia. Mapeé esto a <Leader>u.

Esto probablemente podría ser mejor. Sobrescribe la información de deshacer anterior, por lo que no puede volver a la anterior vez que editó el archivo. Pero no lo he necesitado yo mismo.

superjer
fuente
Tenga en cuenta que he notado un problema con esto: solo guarda el contenido de la sesión actual en el archivo de deshacer. Esto es diferente del comportamiento predeterminado, donde se guarda el contenido de la sesión anterior + toda la información que ya está en el archivo de deshacer ... La forma de solucionar esto sería leer el archivo de deshacer, fusionar los cambios de deshacer y luego escribir el archivo de deshacer ... Pero esto no parece posible ...: - /
Martin Tournoij
@Carpetsmoker Estoy de acuerdo. Sería bueno hacerlo si fuera posible. Resulta que esto me ha funcionado bastante bien durante mucho tiempo. YMMV.
superjer
5

¡Oooh, ooh, finalmente una oportunidad de mostrar este ingenioso comando!

Vim puede "retroceder en el tiempo". Tiene un :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 puede revertir el archivo a un estado anterior. Esto se puede usar de varias maneras.

  • Si puede hacer una estimación aproximada de cuánto tiempo le llevó realizar estos cambios, puede pasar un tiempo en este comando. O, por ejemplo, si sabe que no ha cambiado el archivo (excepto los cambios que desea deshacer) en el último día, puede usar

    :earlier 1d
    
  • Si solo ha escrito (guardado) el archivo una vez desde los cambios que desea deshacer, o no ha guardado el archivo, puede usar

    :earlier 1f
    

    Como se describe en el :helptexto, esto volverá a la versión escrita anteriormente si acaba de escribir el archivo, o volverá a la última vez que se guardó si no tiene ningún cambio guardado.

Esto no responde a su pregunta con precisión, pero suena como un problema de XY.

Perilla de la puerta
fuente
1
Suena como un pequeño problema XY : ¿Por qué? Estoy deshaciendo uy accidentalmente paso los cambios que hice en este momento ... ¿No estoy seguro de cuál podría ser el "problema X" original?
Martin Tournoij
1
Ahora, :earlierpor cierto, pero todavía tengo que adivinar ; tal como necesito adivinar cuando uso el u... En casos probablemente sea un poco mejor, pero preferiría algo más explícito (si es posible).
Martin Tournoij
1
@Carpetsmoker "X" es "Quiero deshacer solo estos cambios que hice más recientemente". "Y" es "¿Cómo puedo deshacer los cambios e ignorar los archivos sin archivar?" Todavía tienes que adivinar, pero dijiste en tu pregunta que los últimos cambios que hiciste fueron la semana pasada o antes, por lo que podrías hacer algo así :ea 5d. También podrías usar el :ea 1fenfoque. En cualquier caso, es mucho menos granular.
Pomo de la puerta
¿"X" e "Y" me parecen una reformulación del mismo problema? Mencioné "semanas" en mi pregunta, pero también podría ser horas o minutos (modificó eso) ... Esta no es una mala respuesta como tal, por cierto, estaba (y estoy) esperando que haya algo mejor ...
Martin Tournoij
Estoy con @Carpetsmoker en este caso. He sabido acerca de: anteriormente por algún tiempo, pero todavía tengo unofile apagado por exactamente la razón descrita en la pregunta.
Rico
4

Actualización 2015-06-28 : arreglé un pequeño error y lo lancé como un complemento . El código del complemento es ligeramente mejor, ya que advierte nuevamente después de mover el cursor; Te recomiendo que uses el complemento.



La respuesta de superjer funciona muy bien, pero tiene el desafortunado efecto secundario de que solo puede deshacer los cambios de la última sesión de Vim, y no todas las sesiones anteriores de Vim.

Esto se debe a que wundosobrescribe el archivo de deshacer; No está fusionado. Que yo sepa, no hay forma de arreglar esto.

Así que aquí está mi solución alternativa, mostrará un gran mensaje de advertencia rojo cuando deshaga los cambios del archivo de deshacer.

Esto es similar a la respuesta de Ingo Karkat , pero no requiere un complemento externo y tiene algunas diferencias sutiles (muestra advertencia en lugar de pitido, no requiere que presione udos veces).

Nota Este sólo modifica el uy <C-r>se une, y no el U, :undoy :redolos comandos.

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

Tengo lo siguiente, que se detiene y emite un pitido cuando deshacer alcanza el contenido del búfer persistente.

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>

Esto se integra con el complemento repeat.vim; requiere mi complemento ingo-library .

Ingo Karkat
fuente
¿Está usando "persistente" para significar "el estado en que se encontraba el archivo cuando se cargó el búfer"? Por lo general, esperaría que "persistiera" significara el estado del archivo actualmente guardado en el disco.
Rich
@Rich: No, tenemos la misma definición. Mis asignaciones no hacen exactamente lo que pide la pregunta, pero aún así la encuentro muy útil.
Ingo Karkat
2

Creo que también aceptaría una respuesta que diera un comando que revierte el archivo al estado en que se encontraba desde la última vez que lo abrió. (O mejor, volví a ese estado de deshacer).

> Rico

Realmente me gustó la idea citada, así que hice el :Revertcomando. Espero que lo encuentres relevante para tu pregunta.

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)

Las funciones auxiliares Get_seqy Real_seq, basadas en unotree , son necesarias porque a undotree()['seq_cur']veces no es suficiente para determinar la posición actual en el árbol de deshacer. Puedes leer más sobre esto aquí .

dsfdsfd
fuente
Esto parece una buena solución. Pero tienes un error en tu au. El vimrc allí es el culpable. Simplemente déjalo afuera
Naumann