"Diff, save or kill" when killing buffers in Emacs - emacs

When trying to kill a buffer that contains changes in Emacs, the message:
" Buffer [buffer] modified; kill anyway? (yes or no)" is displayed.
Instead of this I'd like to have Emacs ask me if I want to:
1. View a diff of what changed,
2. Save the buffer,
3. Kill the buffer.
How?

The answer lies in using advice, because the hooks normally run when killing buffers run after the "buffer modified" prompt you want to change.
The following advice does what you want (I think). A couple of notes:
When running the diff, the original buffer is marked as not modified - but you'll really need to save it.
The other buffer in the diff doesn't get deleted
(defadvice kill-buffer (around my-kill-buffer-check activate)
"Prompt when a buffer is about to be killed."
(let* ((buffer-file-name (buffer-file-name))
backup-file)
;; see 'backup-buffer
(if (and (buffer-modified-p)
buffer-file-name
(file-exists-p buffer-file-name)
(setq backup-file (car (find-backup-file-name buffer-file-name))))
(let ((answer (completing-read (format "Buffer modified %s, (d)iff, (s)ave, (k)ill? " (buffer-name))
'("d" "s" "k") nil t)))
(cond ((equal answer "d")
(set-buffer-modified-p nil)
(let ((orig-buffer (current-buffer))
(file-to-diff (if (file-newer-than-file-p buffer-file-name backup-file)
buffer-file-name
backup-file)))
(set-buffer (get-buffer-create (format "%s last-revision" (file-name-nondirectory file-to-diff))))
(buffer-disable-undo)
(insert-file-contents file-to-diff nil nil nil t)
(set-buffer-modified-p nil)
(setq buffer-read-only t)
(ediff-buffers (current-buffer) orig-buffer)))
((equal answer "k")
(set-buffer-modified-p nil)
ad-do-it)
(t
(save-buffer)
ad-do-it)))
ad-do-it)))

You'll want to write some code to put in the kill-buffer-hooks and write-file-functions lists. Conceptually, what you want to do is
test if the buffer has been modified
display your message and get a
response, and do what's requested
then clear the modified flag so the
normal kill-buffer doesn't come back
and ask again.

Related

emacs function after asynchonous command

I currently use emacs for making and editing LaTeX documents. When compiling, I use an external program to compile into pdf. Right now, with the following code in my .emacs file, emacs will start compiling the document into a pdf whenever I save the file.
(defun auto-compile-latex ()
(save-window-excursion
(async-shell-command (format "cd %s; scons -u" default-directory))))
(add-hook 'LaTeX-mode-hook '(lambda ()
(add-hook 'after-save-hook 'auto-compile-latex nil 'make-it-local)))
I prefer this over M-x compile, because I am more in the habit of saving, and it launches in the background, allowing me to continue working. However, I do not get a prominent notification when the compilation process finishes.
Ideally, I would want to run the following function whenever a compilation process finishes.
(defun latex-compilation-status (exit-code)
(if (/= exit-code 0)
(setq mode-name (propertize mode-name 'face 'font-lock-warning-face))
(setq mode-name (propertize mode-name 'face 'mode-line-highlight))))
That way, I can have the color in the mode line automatically change depending on whether the compilation was successful or not. However, looking through the emacs documentation, I have not found any mention of a hook that gets run after async-shell-command completes. I know that there is the message in the minibuffer stating the exit status of the subprocess, but if I am typing at the time, it is often hard to notice.
Alternatively, I could wait for the shell command to complete, then change the color immediately. However, this then makes the entirety of emacs freeze while compiling, which is not desired.
How would I go about having this indication applied at the end of the compilation, without having emacs freeze during the process?
You should try the command async-start from https://github.com/jwiegley/emacs-async
Thank you.
I ended up getting it using a modified version of lawlist's code.
This now has changes the color when starting to compile,
then changes it again to indicate success or failure.
;Automatically compile any latex documents when saved.
(defun auto-compile-latex ()
(setq mode-name (propertize mode-name 'face 'font-lock-string-face))
(set-process-sentinel
(start-process-shell-command "latex-compile" "latex-compile"
(format "cd %s; scons -u" default-directory))
'latex-compile-sentinel))
;Change the color after compilation. Still need to find the right hook to add it to.
(defun latex-compile-sentinel (process event)
(if (string-equal event "finished\n")
(setq mode-name (propertize mode-name 'face 'mode-line-highlight))
(setq mode-name (propertize mode-name 'face 'font-lock-warning-face))))
;Hooks for latex-mode
(add-hook 'LaTeX-mode-hook '(lambda ()
(add-hook 'after-save-hook 'auto-compile-latex nil 'make-it-local)))
As an aside, the emacs-async package did not work for this use.
I assume that this is because emacs-async starts the secondary functions in a separate process,
with some of the variables not propagated back to the parent process.
Here is another option using set-process-sentinel based upon a prior helpful answer by Francesco. Please do be careful, however, about using a let binding to define a start-process (which [in my lay opinion] is a definite "no no") because that would cause the process to begin immediately before its time. [FYI: Nicolas has gotten me out of quite a few jams in the past, so please be sure to take a good hard look at his solution also.]
Emacs: if latexmk finishes okay, then show pdf, else display errors
You would just add your function to the last part of the function dealing with success -- i.e., (when (= 0 (process-exit-status p)) . . .:
(defun latexmk ()
".latexmkrc contains the following entries (WITHOUT the four backslashes):
$pdflatex = 'pdflatex -file-line-error -synctex=1 %O %S && (cp \"%D\" \"%R.pdf\")';
$pdf_mode = 1;
$out_dir = '/tmp';"
(interactive)
(setq tex-file buffer-file-name)
(setq pdf-file (concat "/tmp/" (car (split-string
(file-name-nondirectory buffer-file-name) "\\.")) ".pdf"))
(setq line (format "%d" (line-number-at-pos)))
(setq skim "/Applications/Skim.app/Contents/SharedSupport/displayline")
(setq tex-output (concat "*" (file-name-nondirectory buffer-file-name) "*") )
(setq latexmk "/usr/local/texlive/2012/texmf-dist/scripts/latexmk/latexmk.pl")
(setq latexmkrc "/Users/HOME/.0.data/.0.emacs/.latexmkrc")
(if (buffer-modified-p)
(save-buffer))
(delete-other-windows)
(set-window-buffer (split-window-horizontally) (get-buffer-create tex-output))
(with-current-buffer tex-output (erase-buffer))
(set-process-sentinel
(start-process "compile" tex-output latexmk "-r" latexmkrc tex-file)
(lambda (p e) (when (= 0 (process-exit-status p))
(start-process "displayline" nil skim "-b" line pdf-file tex-file)
(switch-to-buffer (get-file-buffer tex-file))
(if (get-buffer-process (get-buffer tex-output))
(process-kill-without-query (get-buffer-process (get-buffer tex-output))))
(kill-buffer tex-output)
(delete-other-windows)))))
You could also arrange for your auto-compile-latex to run compile.

Scripting magit timing problems

C-x v = vc-diff is good. However, I can work with the diff
directly if the diff was shown in a magit-status buffer.
I've tried to do just that here:
(defvar le::vc-diff-data nil)
(defun le::magit-go-to-change-once ()
(destructuring-bind (filename orig-buff relative-name) le::vc-diff-data
(pop-to-buffer "*magit: magit*")
(goto-char (point-min))
(if (and (search-forward-regexp "^Changes:$" nil t)
;; WIP fix
(progn (magit-show-level-2) t)
(search-forward relative-name nil t))
(progn (recenter-top-bottom 0)
;; WIP fix me here
(magit-show-level-4)
)
;; no diff
(pop-to-buffer orig-buf)
(message "no diff found.")))
(remove-hook 'magit-refresh-status-hook #'le::magit-go-to-change-once))
(defadvice vc-diff (around magit-redirect activate compile)
"redirect to magit"
(let* ((vc-info (vc-deduce-fileset t))
(filename (buffer-file-name))
(orig-buf (current-buffer))
(relative-name (replace-regexp-in-string
(concat "\\`"
(regexp-quote (expand-file-name (locate-dominating-file filename ".git"))))
"" filename)))
(if (string-equal "Git" (car vc-info))
(progn
(setq le::vc-diff-data (list filename orig-buf relative-name))
(add-hook 'magit-refresh-status-hook #'le::magit-go-to-change-once)
(call-interactively 'magit-status))
ad-do-it)))
However the "magit-show-level*" function fails. It works when I use
eval-expression in the magit buffer though. So maybe this is a timing issue
and I have to hook in somewhere else.
As said in my comment, the hook is called at a time when section related function will not work. You could try magit from there: https://github.com/vanicat/magit/tree/t/refresh-stasus-hook, your code should work with it.
Bye the way your proposition is interesting, and integrate it into magit contrib's proposition could be great.

dired-x: How to set "Kill buffer of ..., too?" to yes without confirmation?

If you delete a file foo in dired-x, you get asked Kill buffer of foo, too?. How can I skip this question and always answer it with yes?
You can advise the dired-delete-entry function so that any file buffers are closed before the deletion:
(defadvice dired-delete-entry (before force-clean-up-buffers (file) activate)
(kill-buffer (get-file-buffer file)))
The Elisp manual describes advising as "cleaner than redefining the whole function" and it is less likely to break if the definition of the function changes in the future.
Simply redefine dired-clean-up-after-deletion in dired-x.el.
;; redefine the definition in dired-x.el, so that we are not prompted
;; to remove buffers that were associated with deleted
;; files/directories
(eval-after-load "dired-x" '(defun dired-clean-up-after-deletion (fn)
"My. Clean up after a deleted file or directory FN.
Remove expanded subdir of deleted dir, if any."
(save-excursion (and (cdr dired-subdir-alist)
(dired-goto-subdir fn)
(dired-kill-subdir)))
;; Offer to kill buffer of deleted file FN.
(if dired-clean-up-buffers-too
(progn
(let ((buf (get-file-buffer fn)))
(and buf
(save-excursion ; you never know where kill-buffer leaves you
(kill-buffer buf))))
(let ((buf-list (dired-buffers-for-dir (expand-file-name fn)))
(buf nil))
(and buf-list
(while buf-list
(save-excursion (kill-buffer (car buf-list)))
(setq buf-list (cdr buf-list)))))))
;; Anything else?
))
Other answers are stale, Since 26.1 Emacs provide a option to skip confirmation
(setq dired-clean-confirm-killing-deleted-buffers nil)

Emacs Lisp buffer not running font lock mode until opened by user

My problem is I am opening a buffer using (set-buffer (find-tag-noselect (current-word))) and then I try to copy some text out of that buffer. The text that I get back has only the properties (fontified nil). find-tag-noselect automatically opens the buffer found in the TAGS file but it seems it does not run the font lock mode over it. When I manually switch to this buffer after it has been opened and then run the function again when it copies the text it has all the correct text properties attached. So what do I need to do to have this buffer completely initialized so that the correct syntax highlighting will be copied in?
(defvar newline-string "
")
(defun get-initial-indent-regexp-python()
"Gets the initial amount of spaces for the function we are looking at, does not account for tabs"
(concat "^" (get-current-indent-string) (concat "[^ #" newline-string "]")))
(defun get-end-of-function-python(spaces-regex)
"Gets the point at the end of a python block"
(save-excursion
(forward-line 1)
(while (and (not (looking-at spaces-regex)) (equal (forward-line 1) 0)))
(point)))
(defun get-point-at-end-of-function ()
"This might be better served checking the major mode."
(setq extension (file-name-extension (buffer-file-name)))
(if (equal extension "py")
(get-end-of-function-python (get-initial-indent-regexp-python))))
(defun inline-function ()
"Must change to overlays, be able to toggle visibility"
(interactive)
(let (text indent-string)
; clean all overlays without attached buffer
(save-excursion
(set-buffer (find-tag-noselect (current-word)))
(setq text (buffer-substring (point) (get-point-at-end-of-function))))
(setq text (concat newline-string text))
(save-excursion
(move-end-of-line nil)
(let (overlay)
(setq overlay (make-overlay (point) (+ (point) 1) (current-buffer)))
(overlay-put overlay 'display text)
(setq inline-func-overlays (cons overlay inline-func-overlays))))))
What's happening is that font-lock is done on-the-fly, so only the displayed parts of the buffer get "fontified". If you want/need to overrule this optimization, you need different functions depending on the circumstance (depending on how font-lock happens to be configured). We should add a new font-lock-ensure-fontified function for that, but in the mean time, you can take ps-print-.el as an example:
(defun ps-print-ensure-fontified (start end)
(cond ((and (boundp 'jit-lock-mode) (symbol-value 'jit-lock-mode))
(jit-lock-fontify-now start end))
((and (boundp 'lazy-lock-mode) (symbol-value 'lazy-lock-mode))
(lazy-lock-fontify-region start end))))
I'm not exactly sure what you're trying to do, but set-buffer does not display the buffer, so its effect ends when the current command terminates. It's generally useful only for temporary buffer switches inside a function and I guess this is the reason it doesn't run font-lock on the buffer. When you manually go to the buffer you're probably using a different function - switch-to-buffer.
Try explicitly calling 'font-lock-fontify-buffer'.

Emacs - Can't get buffer-offer-save working

I would like to have Emacs ask me whether I want to save a modified buffer, when that buffer is not associated with a file. To open a new buffer (not visiting a file) I have the following function in my .emacs file:
;; Creates a new empty buffer
(defun new-empty-buffer ()
"Opens a new empty buffer."
(interactive)
(let ((buf (generate-new-buffer "untitled")))
(switch-to-buffer buf)
(funcall (and default-major-mode))
(setq buffer-offer-save t)))
I thought setting "buffer-offer-save" to something not nil would made the trick. But whenever I kill the buffer with "kill-this-buffer", it gets instantly killed without asking anything.
This happens on GNU Emacs 23.1.1
Any ideas?
Thanks,
W
Edited to add use of buffers-offer-save. Note: the variable buffer-offer-save is only used upon exiting Emacs.
You can start with this code and customize it to what you want:
(add-to-list 'kill-buffer-query-functions 'ask-me-first)
(defun ask-me-first ()
"prompt when killing a buffer"
(if (or buffer-offer-save
(eq this-command 'kill-this-buffer)
(and (buffer-modified-p) (not (buffer-file-name))))
(y-or-n-p (format "Do you want to kill %s without saving? " (buffer-name)))
t))
Upon further reflection, that is a bit heavy-handed because you get prompted for all buffers that get killed, and there are often lots of temporary buffers that Emacs uses. If you just want to be prompted when you try to interactively kill a buffer (that isn't associated with a file).
You can use this advice which only prompts you when you're interactively trying to kill a buffer:
(defadvice kill-buffer (around kill-buffer-ask-first activate)
"if called interactively, prompt before killing"
(if (and (or buffer-offer-save (interactive-p))
(buffer-modified-p)
(not (buffer-file-name)))
(let ((answ (completing-read
(format "Buffer '%s' modified and not associated with a file, what do you want to do? (k)ill (s)ave (a)bort? " (buffer-name))
'("k" "s" "a")
nil
t)))
(when (cond ((string-match answ "k")
;; kill
t)
((string-match answ "s")
;; write then kill
(call-interactively 'write-file)
t)
(nil))
ad-do-it)
t)
;; not prompting, just do it
ad-do-it))
Modifying 'new-empty-buffer seems to make it work as I intended with Trey's defadvice.
;; Creates a new empty buffer
(defun new-empty-buffer ()
"Opens a new empty buffer."
(interactive)
(let ((buf (generate-new-buffer "untitled")))
(switch-to-buffer buf)
(funcall (and default-major-mode))
(put 'buffer-offer-save 'permanent-local t)
(setq buffer-offer-save t)))
This makes buffer-offer-save permanent local in our new buffer, so it won't get killed with the rest of the local variables when switching major modes.
buffer-offer-save asking on exiting Emacs but not on closing a buffer manually doesn't make sense, so why not “enlarge” its responsibilities?
(defadvice kill-buffer (around kill-buffer-ask activate)
"If `buffer-offer-save' is non-nil and a buffer is modified,
prompt before closing."
(if (and buffer-offer-save (buffer-modified-p))
(when (yes-or-no-p "The document isn't saved. Quit? ")
ad-do-it)
ad-do-it))
It will not prompt if untitled buffer is newly created. It will not prompt if you use kill-buffer from Elisp. It will not prompt on Emacs system buffers like *Messages*. But it will prompt if you created an empty buffer and wrote something in it.
See also my answer on creating an empty buffer.