Reverter todos os buffers abertos (e ignorar erros)

12

Ao trabalhar em um projeto sob controle de versão com o git, geralmente quero fazer algumas coisas em um shell que afetam muitos dos meus arquivos abertos, e depois reverter todos os buffers que tenho aberto para garantir que não derrube acidentalmente a nova versão com o que eu tinha aberto. Sei que magitpode ser útil aqui, mas estou acostumado com meu fluxo de trabalho no shell e gostaria de mantê-lo por enquanto. Então, em vez disso, eu gostaria de reverter todos os buffers abertos e talvez fechar todos os que pararam de existir (por exemplo, devido a git checkoutuma ramificação que não possui mais esse arquivo).

Eu tenho o seguinte trecho de elisp que peguei em uma pesquisa no Google:

(defun revert-all-buffers ()
  "Refreshes all open buffers from their respective files"
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (when (and (buffer-file-name buffer) 
                 (not (buffer-modified-p buffer)))
        (set-buffer buffer)
        (revert-buffer t t t))
      (setq list (cdr list))
      (setq buffer (car list))))
  (message "Refreshed open files"))

Mas esta quebra se ela atinge um erro em um dos meus arquivos abertos, ou seja, quando revertendo B1, B2, B3, ..., Bnum erro ao tentar reverter B2previne B3- Bnde ser revertido.

Como posso dizer ao emacs para ignorar os erros que aparecerem neste caso? Eu não quero usar global-auto-revert-modeporque cada reversão aciona algumas coisas pesadas, como o meu auto-complete e o verificador de sintaxe, analisando novamente o arquivo, suspendendo o emacs por mais ou menos um segundo.

Patrick Collins
fonte
Que tipo de erro impede a reversão do B2buffer no seu exemplo. Uso uma função muito semelhante (provavelmente derivada desse trecho) e funcionou bem.
Kaushal Modi
@ Kaushal: parece que o "arquivo não existe mais" existe, e / ou erros gerados por pacotes que eu tenho que reverter o buffer reverso. Percebi principalmente que, depois de executá-lo, ainda recebo a mensagem "O arquivo foi alterado desde a última visita!" onC-x s
Patrick Collins
"file no longer exists".. aha! minha versão corrige isso :) Em breve.
Kaushal Modi

Respostas:

12

Original

Aqui está minha versão ligeiramente aprimorada do snippet na pergunta. Revendo meu histórico de VC, confirmo que o snippet abaixo começou como o snippet publicado pelo OP. Então, eu pago atributo a isso.

Aqui está o código que foi estável para mim:

(defun modi/revert-all-file-buffers ()
  "Refresh all open buffers from their respective files."
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (let ((filename (buffer-file-name buffer)))
        ;; Revert only buffers containing files, which are not modified;
        ;; do not try to revert non-file buffers like *Messages*.
        (when (and filename
                   (not (buffer-modified-p buffer)))
          (if (file-exists-p filename)
              ;; If the file exists, revert the buffer.
              (with-current-buffer buffer
                (revert-buffer :ignore-auto :noconfirm :preserve-modes))
            ;; If the file doesn't exist, kill the buffer.
            (let (kill-buffer-query-functions) ; No query done when killing buffer
              (kill-buffer buffer)
              (message "Killed non-existing file buffer: %s" filename)))))
      (setq buffer (pop list)))
    (message "Finished reverting buffers containing unmodified files.")))

Atualizar

Aqui está uma versão melhorada e melhor documentada acima, depois de analisar a solução da @ Drew .

(defun modi/revert-all-file-buffers ()
  "Refresh all open file buffers without confirmation.
Buffers in modified (not yet saved) state in emacs will not be reverted. They
will be reverted though if they were modified outside emacs.
Buffers visiting files which do not exist any more or are no longer readable
will be killed."
  (interactive)
  (dolist (buf (buffer-list))
    (let ((filename (buffer-file-name buf)))
      ;; Revert only buffers containing files, which are not modified;
      ;; do not try to revert non-file buffers like *Messages*.
      (when (and filename
                 (not (buffer-modified-p buf)))
        (if (file-readable-p filename)
            ;; If the file exists and is readable, revert the buffer.
            (with-current-buffer buf
              (revert-buffer :ignore-auto :noconfirm :preserve-modes))
          ;; Otherwise, kill the buffer.
          (let (kill-buffer-query-functions) ; No query done when killing buffer
            (kill-buffer buf)
            (message "Killed non-existing/unreadable file buffer: %s" filename))))))
  (message "Finished reverting buffers containing unmodified files."))

Referência

Kaushal Modi
fonte
5

Outro:

(defun revert-all-no-confirm ()
  "Revert all file buffers, without confirmation.
Buffers visiting files that no longer exist are ignored.
Files that are not readable (including do not exist) are ignored.
Other errors while reverting a buffer are reported only as messages."
  (interactive)
  (let (file)
    (dolist (buf  (buffer-list))
      (setq file  (buffer-file-name buf))
      (when (and file  (file-readable-p file))
        (with-current-buffer buf
          (with-demoted-errors "Error: %S" (revert-buffer t t)))))))
Desenhou
fonte
Obrigado. Estou roubando o dolistestilo para substituir care pop. Engraçado como você pode continuar a melhorar sua configuração como você aprende mais elisp :)
Kaushal Modi
@KaushalModi Foi por isso que eu publiquei, em parte. ;-)
Drew
1

Aceitei a resposta de Kausal, uma vez que era o mais próximo do que eu queria, mas peguei parte da solução de Drew também. Eu envolvi revert-bufferem with-demoted-errorse deixou cair o :preserve-modesparâmetro para que o meu verificador de sintaxe seria re-analisar todos os meus arquivos abertos. Eu também o deixei matar arquivos modificados e não modificados, já que geralmente encontro problemas ao C-x sdigitar acidentalmente git checkoutum arquivo modificado aberto.

A versão final é:

(defun revert-all-buffers ()
  "Refresh all open buffers from their respective files."
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (let ((filename (buffer-file-name buffer)))
        ;; Revert only buffers containing files, which are not modified;
        ;; do not try to revert non-file buffers like *Messages*.
        (when filename
          (if (file-exists-p filename)
              ;; If the file exists, revert the buffer.
              (with-demoted-errors "Error: %S"
                (with-current-buffer buffer
                  (revert-buffer :ignore-auto :noconfirm)))
            ;; If the file doesn't exist, kill the buffer.
            (let (kill-buffer-query-functions) ; No query done when killing buffer
              (kill-buffer buffer)
              (message "Killed non-existing file buffer: %s" buffer))))
        (setq buffer (pop list)))))
  (message "Finished reverting non-file buffers."))
Patrick Collins
fonte
Adicionado mensagens de progresso desde que este pode aparecer jeito com muitos arquivos abertos: emacs.stackexchange.com/a/50730/2418
ideasman42
1

Eu consertaria isso com um condition-caseou ignore-errors(documentos aqui ). Não sei exatamente o que você deseja que faça ; se você quiser fazer algo com erros, se pode usar condition-casepara especificar o resultado ou se pode ignore-errorscontinuar apenas. Algo como:

(defun revert-all-buffers ()
  "Refreshes all open buffers from their respective files"
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (when (and (buffer-file-name buffer) 
                 (not (buffer-modified-p buffer)))
        (set-buffer buffer)
        (ignore-errors (revert-buffer t t t)))
      (setq list (cdr list))
      (setq buffer (car list))))
  (message "Refreshed open files"))
Gastove
fonte
0

Com base na resposta de @ Drew, com adições:

  • Relatório de progresso (pois pode ser lento com muitos arquivos abertos) .
  • Limpar estado de desfazer (com suporte para pacotes que carregam histórico de desfazer ao recarregar o buffer - desfazer-fu-sessão por exemplo) .
(defun revert-all-buffers ()
  "Refresh all open buffers from their respective files.

Buffers which no longer exist are closed.

This can be useful when updating or checking out branches outside of Emacs."
  (interactive)
  (let* ((filename-and-buffer-list ;; Pairs of '(filename . buf)'.
          (let ((temp-list nil))
            (dolist (buf (buffer-list))
              (let ((filename (buffer-file-name buf)))
                (when filename
                  (push (cons filename buf) temp-list))))
            temp-list))

         (count (length filename-and-buffer-list))
         (count-final 0)
         (count-close 0)
         (count-error 0)
         ;; Keep text at a fixed width when redrawing.
         (format-count
          (format "%%%dd" (length (number-to-string count))))
         (format-text
          (concat "Reverting [" format-count " of " format-count "] %3d%%: %s"))
         (index 1))

    (message "Begin reverting %d buffers..." count)
    (while filename-and-buffer-list
      (pcase-let ((`(,filename . ,buf) (pop filename-and-buffer-list)))
        ;; Revert only buffers containing files, which are not modified;
        ;; do not try to revert non-file buffers such as '*Messages*'.
        (message format-text
                 index count (round (* 100 (/ (float index) count))) filename)
        (if (file-exists-p filename)
            ;; If the file exists, revert the buffer.
            (if (with-demoted-errors "Error: %S"
                  (with-current-buffer buf
                    (let ((no-undo (eq buffer-undo-list t)))

                      ;; Disable during revert.
                      (unless no-undo
                        (setq buffer-undo-list t)
                        (setq pending-undo-list nil))

                      (unwind-protect
                          (revert-buffer :ignore-auto :noconfirm)

                        ;; Enable again (always run).
                        (unless no-undo
                          ;; It's possible a plugin loads undo data from disk,
                          ;; check if this is still unset.
                          (when (and (eq buffer-undo-list t)
                                     (null pending-undo-list))
                            (setq buffer-undo-list nil))))))
                  t)
                (setq count-final (1+ count-final))
              (setq count-error (1+ count-error)))

          ;; If the file doesn't exist, kill the buffer.
          (let (kill-buffer-query-functions) ;; No query done when killing buffer.
            (message "Closing non-existing file buffer: %s" buf)
            (kill-buffer buf)
            (setq count-close (1+ count-close))))
        (setq index (1+ index))))
    (message
     (concat
      "Finished Revert All: " (format "%d buffer(s)" count-final)
      (if (zerop count-close)
          ""
        (format ", %d closed" count-close))
      (if (zerop count-error)
          ""
        (format ", %d error (see message buffer)" count-error))))))
ideasman42
fonte