Could anyone please give me a hand concatenating double quotes:
In this example, I'm writing a function to be used with Emacs on a Windows operating system. File names with spaces need to be enclosed in double-quotes.
The buffer-file-name is: c:/Documents and Settings/All Users/Desktop/foo.tex
I'm trying to use:
(setq pdf-file
(concat "\"" (car (split-string (buffer-file-name) "\\.tex")) ".pdf" "\""))
When I call (start-process "display" nil c:/SumatraPDF.exe pdf-file), the pdf viewer tries to open this instead:
c:\Documents and Settings\All Users\Desktop"c:\Documents and Settings\All Users\Desktop\foo.pdf"
EDIT: This is the function that I was trying to modify. As set forth in my answer below, the error was caused by my unwittingly having double-double-quoted the file name -- i.e., start-process treats a variable as being double-quoted, so there was never any need for me to concatenate another set of double quotes.
(defun latexmk ()
".latexmkrc contains the following entries:
$pdflatex = 'pdflatex -file-line-error -synctex=1 %O %S';
$pdf_mode = 1;
$recorder = 0;
$clean_ext = 'synctex.gz synctex.gz(busy) aux fdb_latexmk log';"
(interactive)
(setq tex-file (file-name-nondirectory buffer-file-name))
(setq base-file (car (split-string (file-name-nondirectory buffer-file-name) "\\.tex")))
(setq pdf-file (concat base-file ".pdf"))
(setq line (format "%d" (line-number-at-pos)))
(setq sumatra "C:/SumatraPDF/SumatraPDF.exe")
(setq tex-output (concat "*" (file-name-nondirectory buffer-file-name) "*") )
(setq latexmk "c:/texlive/2013/bin/win32/latexmk.exe")
(setq latexmkrc "c:/Documents and Settings/Administrator/Application Data/.emacs.d/.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))
(start-process "tskill" nil "c:/WINDOWS/system32/tskill.exe" "SumatraPDF")
(set-process-sentinel
(start-process "deep-clean" nil latexmk "-C" "-r" latexmkrc tex-file)
(lambda (p e) (when (= 0 (process-exit-status p))
(set-process-sentinel
(start-process "compile" tex-output latexmk "-r" latexmkrc tex-file)
(lambda (p e) (when (= 0 (process-exit-status p))
(set-process-sentinel
(start-process "displayline" nil sumatra "-forward-search" tex-file line pdf-file)
(lambda (p e) (when (= 0 (process-exit-status p))
(start-process "clean" nil latexmk "-c" "-r" latexmkrc 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)))))))))))
I'm guessing you want:
(replace-regexp-in-string
"\\.tex$"
".pdf"
"c:/Documents and Settings/All Users/Desktop/foo.tex")
(setq pdf-file (concat (file-name-sans-extension (buffer-file-name)) ".pdf"))
Also, if you need to quote an argument passed to a shell, use shell-quote-argument.
start-process treats a variable as if it were double-quoted. In essence, my variable ended up being double-double-quoted -- which is what was preventing me from using the absolute path to the file. So, in a nutshell, when using a variable with start-process, it need not be double-quoted.
Thank you to everyone who helped me to troubleshoot the issue -- greatly appreciated !
Here is the revised working function:
(defun latexmk ()
".latexmkrc contains the following entries:
$pdflatex = 'pdflatex -file-line-error -synctex=1 %O %S';
$pdf_mode = 1;
$recorder = 0;
$clean_ext = 'synctex.gz synctex.gz(busy) aux fdb_latexmk log';"
(interactive)
(setq tex-file (buffer-file-name))
(setq base-file (car (split-string (buffer-file-name) "\\.tex")))
(setq pdf-file (concat base-file ".pdf"))
(setq line (format "%d" (line-number-at-pos)))
(setq sumatra "c:/Program Files/SumatraPDF/SumatraPDF.exe")
(setq tex-output (concat "*" (file-name-nondirectory buffer-file-name) "*") )
(setq latexmk "c:/texlive/2013/bin/win32/latexmk.exe")
(setq latexmkrc "c:/Documents and Settings/Administrator/Application Data/.emacs.d/.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))
(start-process "tskill" nil "c:/WINDOWS/system32/tskill.exe" "SumatraPDF")
(set-process-sentinel
(start-process "deep-clean" nil latexmk "-C" "-r" latexmkrc tex-file)
(lambda (p e) (when (= 0 (process-exit-status p))
(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 sumatra "-forward-search" tex-file line pdf-file)
(start-process "clean" nil latexmk "-c" "-r" latexmkrc 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))))))))
Related
Could anyone please give me a hand to briefly pop-up a dired buffer for the purposes of read-file-name:
(defun dired-insert-file ()
(interactive)
(setq filename (dired-read-file-name "~/Desktop"))
(kill-buffer dired)
(get-buffer-create "*foo*")
(set-buffer "*foo*")
(insert-file filename))
EDIT: Revised example:
(require 'dired)
(defvar open-with-variable nil)
(defvar save-as-variable nil)
(defvar save-as-buffer-filename nil)
(defun dired-read-file-name (&optional directory)
(let* (
output-filename
(working-buffer (buffer-name)))
(if directory
(dired directory)
(dired nil))
(if save-as-buffer-filename
(progn
(goto-char (point-min))
(re-search-forward (file-name-nondirectory save-as-buffer-filename) nil t)))
(recursive-edit)
(switch-to-buffer working-buffer)
output-filename))
(defun dired-insert-file ()
(interactive)
(let* (
(save-as-variable t)
(lawlist-filename (dired-read-file-name)))
(insert-file-contents lawlist-filename)))
;; Open with external application.
(define-key dired-mode-map (kbd "C-c o") (lambda () (interactive)
(let* (
(open-with-variable t)
(lawlist-filename (dired-get-file-for-visit))
(application (dired-read-file-name "/Applications")))
(start-process "external-application" nil "open" "-a" application lawlist-filename))))
(defun lawlist-save-as ()
(interactive)
(let* (
save-as-filename
(save-as-variable t)
(save-as-buffer-filename (if (buffer-file-name) (buffer-file-name)))
(proposed-filename (dired-read-file-name)))
(when proposed-filename ;; needed if aborting recursive-edit
(setq save-as-filename (read-string "Save-As: "
(if (file-directory-p proposed-filename)
(concat proposed-filename (buffer-name))
proposed-filename)))
(when (and save-as-filename (file-exists-p save-as-filename))
(or (y-or-n-p (format "File `%s' exists; overwrite? " save-as-filename))
(error "Canceled")))
(set-visited-file-name save-as-filename)
(set-buffer-modified-p t)
(and (buffer-file-name)
(file-writable-p buffer-file-name)
(setq buffer-read-only nil))
(save-buffer))))
;; delete the buffer after selecting file | application | directory
(define-key dired-mode-map (kbd "<return>") (lambda () (interactive)
(select-file-application-directory t)))
;; do not delete the buffer after selecting file | application | directory
(define-key dired-mode-map (kbd "<C-M-s-return>") (lambda () (interactive)
(select-file-application-directory nil)))
;; select file | application | directory
(defun select-file-application-directory (&optional delete-buffer)
(let* (
(fn-list (dired-get-marked-files))
(rfn-list (mapcar (function dired-make-relative) fn-list))
(dired-one-file (and (consp fn-list) (null (cdr fn-list)) (car fn-list)))
(input-filename (if dired-one-file dired-one-file fn-list))
(ext
(cond
((stringp input-filename)
(file-name-extension input-filename))
((listp input-filename)
(file-name-extension (car input-filename)))))
(path (if (stringp input-filename) (file-name-directory input-filename)))
(dired-buffer-name (buffer-name))
(msword "/Applications/Microsoft Office 2011/Microsoft Word.app/Contents/MacOS/Microsoft Word")
(ooo "/Applications/OpenOffice.org.app")
(excel "/Applications/Microsoft Office 2011/Microsoft Excel.app/Contents/MacOS/Microsoft Excel")
(adobe "/Applications/Adobe Acrobat 9 Pro/Adobe Acrobat Pro.app/Contents/MacOS/AdobeAcrobat")
(preview "/Applications/Preview.app/Contents/MacOS/Preview")
(skim "/Applications/Skim.app/Contents/MacOS/Skim")
(input-regexp '("odt" "wpd" "docx" "doc" "xls" "pdf" "tif" "bmp" "jpg"))
(pdf-regexp '("pdf" "tif" "bmp" "jpg"))
(ooo-regexp '("odt" "wpd"))
(msword-regexp '("doc" "docx")))
(cond
;; only use current path a save-as situation.
((and
(equal input-filename (concat path "."))
save-as-variable)
(setq output-filename (expand-file-name default-directory))
(if delete-buffer (kill-buffer dired-buffer-name))
(throw 'exit nil))
;; save-as (stringp) | dired-insert-file
((and
(stringp input-filename)
(not (file-directory-p input-filename))
(file-exists-p input-filename)
(not (equal input-filename (concat path ".")))
save-as-variable)
(setq output-filename input-filename)
(if delete-buffer (kill-buffer dired-buffer-name))
(throw 'exit nil))
;; open just one file, except input-regexp
((and
(stringp input-filename)
(not (file-directory-p input-filename))
(file-exists-p input-filename)
(not (equal input-filename (concat path ".")))
(not save-as-variable)
(not (regexp-match-p input-regexp ext)))
(if delete-buffer (kill-buffer dired-buffer-name))
(find-file input-filename))
;; open numerous files, except input-regexp
((and
(listp input-filename)
(not (regexp-match-p input-regexp ext)))
(if delete-buffer (kill-buffer dired-buffer-name))
(mapc 'find-file input-filename))
;; open OpenOfficeOrg
((and
(stringp input-filename)
(not (file-directory-p input-filename))
(file-exists-p input-filename)
(not (equal input-filename (concat path ".")))
(not save-as-variable)
(regexp-match-p ooo-regexp ext))
(start-process "ooo-view" nil "open" "-a" ooo input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
;; open msword
((and
(stringp input-filename)
(not (file-directory-p input-filename))
(file-exists-p input-filename)
(not (equal input-filename (concat path ".")))
(not save-as-variable)
(regexp-match-p msword-regexp ext))
(start-process "msword-view" nil "open" "-a" msword input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
;; open excel
((and
(stringp input-filename)
(not (file-directory-p input-filename))
(file-exists-p input-filename)
(not (equal input-filename (concat path ".")))
(not save-as-variable)
(equal ext "xls"))
(start-process "excel-view" nil "open" "-a" excel input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
;; *.pdf -- open just one *.pdf file.
((and
(stringp input-filename)
(not (file-directory-p input-filename))
(file-exists-p input-filename)
(not (equal input-filename (concat path ".")))
(not save-as-variable)
(regexp-match-p pdf-regexp ext))
(lawlist-message "[a]dobe | [p]review | [s]kim")
(let* ((select-pdf-viewer (read-char-exclusive)))
(cond
((eq select-pdf-viewer ?a)
(start-process "pdf-with-adobe" nil "open" "-a" adobe input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
((eq select-pdf-viewer ?p)
(start-process "pdf-with-preview" nil "open" "-a" preview input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
((eq select-pdf-viewer ?s)
(start-process "pdf-with-adobe" nil "open" "-a" skim input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
(t (message "You have exited the sub-function.")) )) )
;; *.pdf -- open more than just one *.pdf file.
((and
(listp input-filename)
(not save-as-variable)
(regexp-match-p pdf-regexp ext))
(lawlist-message "[a]dobe | [p]review | [s]kim")
(let* ((select-pdf-viewer (read-char-exclusive)))
(cond
((eq select-pdf-viewer ?a)
(mapcar (lambda (x)
(start-process "pdf-with-adobe" nil "open" "-a" adobe x) )
input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
((eq select-pdf-viewer ?p)
(mapcar (lambda (x)
(start-process "pdf-with-preview" nil "open" "-a" preview x) )
input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
((eq select-pdf-viewer ?s)
(mapcar (lambda (x)
(start-process "pdf-with-adobe" nil "open" "-a" skim x) )
input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
(t (message "You have exited the sub-function.")) )) )
;; open with external application, because the `open-with-variable` is t.
((and
open-with-variable
(equal ext "app"))
(setq output-filename input-filename)
(if delete-buffer (kill-buffer dired-buffer-name))
(throw 'exit nil))
;; Enter the directory; or, open an application
((and
(file-directory-p input-filename)
(not (equal input-filename (concat path ".")))
(not open-with-variable))
(if (equal ext "app")
(progn
(message "[d]eeper | [o]pen")
(let* ((deeper-open (read-char-exclusive)))
(cond
((eq deeper-open ?d)
(dired-find-file)
(goto-char (point-min))
(re-search-forward " \\.\\.$" nil t)
(if delete-buffer (kill-buffer dired-buffer-name)))
((eq deeper-open ?o)
(start-process "application" nil "open" "-a" input-filename)
(if delete-buffer (kill-buffer dired-buffer-name)))
(t (message "You have exited the sub-function.")) )) )
(dired-find-file)
(goto-char (point-min))
(re-search-forward " \\.\\.$" nil t)
(if delete-buffer (kill-buffer dired-buffer-name)))) )))
;; https://github.com/kentaro/auto-save-buffers-enhanced
;; `regexp-match-p` function modified by #sds on stackoverflow
;; http://stackoverflow.com/questions/20343048/distinguishing-files-with-extensions-from-hidden-files-and-no-extensions
(defun regexp-match-p (regexps string)
(and string
(catch 'matched
(let ((inhibit-changing-match-data t)) ; small optimization
(dolist (regexp regexps)
(when (string-match regexp string)
(throw 'matched t)))))))
(defun lawlist-message (input)
(interactive)
(message
(propertize input 'face 'font-lock-warning-face)))
You'll want to look into recursive-edit: pop up a dired buffer in which you add a way to exit (which works by performing a (throw 'exit <value>)), and then call (recursive-edit) which will return the <value> passed to throw.
I'm having difficulty trying to add an if file-exists-p condition inside the interactive list of this print-to-pdf function. The error message is: file-name-sans-extension: Wrong type argument: stringp, t. I have commented out the section that works, but which nevertheless overwrites an existing file without prompting for a yes or no. I've tried replacing hello-world with pdf-file-name, but that didn't fix the error.
Emacs Trunk developer build --with-ns on OSX, using ns-read-file-name, likes to overwrite files without prompting unless an additional file-exists-p is included in the function.
(defun print-to-pdf (pdf-file-name)
"Print the current file to the given file."
;; (interactive (list (ns-read-file-name "Write PDF file: " "~/" nil ".pdf")
(interactive (list
(let ((hello-world (ns-read-file-name "Write PDF file: " "~/" nil ".pdf")))
(if (file-exists-p hello-world)
(or (yes-or-no-p (format "File %s exists. Save anyway? " hello-world))
(error ""))))
))
(let (
(ps-file-name (concat (file-name-sans-extension pdf-file-name) ".ps"))
(wbuf (generate-new-buffer "*Wrapped*"))
(sbuf (current-buffer)))
(jit-lock-fontify-now)
(save-current-buffer
(set-buffer wbuf)
(insert-buffer sbuf)
(longlines-mode t)
(harden-newlines)
(message (buffer-name sbuf))
(spool-buffer-given-name (buffer-name sbuf))
(kill-buffer wbuf)
(switch-to-buffer "*PostScript*")
(write-file ps-file-name)
(kill-buffer (current-buffer)))
(call-process "/usr/local/bin/ps2pdf14" nil nil nil ps-file-name pdf-file-name)
(delete-file ps-file-name)
(message "PDF saved to %s" pdf-file-name))
)
Why do you want to add it to interactive in the first place? Wouldn't something like this work for you?
(defun print-to-pdf (pdf-file-name)
"Print the current buffer to the given file as PDF."
(interactive (list (ns-read-file-name "Write PDF file: " "~/" nil ".pdf")))
(when (and pdf-file-name
(or (not (file-exists-p pdf-file-name))
(yes-or-no-p "File exists. Overwrite? ")))
(let ((ps-file-name (concat (file-name-sans-extension pdf-file-name) ".ps"))
(wbuf (generate-new-buffer "*Wrapped*"))
(sbuf (current-buffer)))
(jit-lock-fontify-now)
(save-current-buffer
(set-buffer wbuf)
(insert-buffer sbuf)
(longlines-mode t)
(harden-newlines)
(message (buffer-name sbuf))
(spool-buffer-given-name (buffer-name sbuf))
(kill-buffer wbuf)
(switch-to-buffer "*PostScript*")
(write-file ps-file-name)
(kill-buffer (current-buffer)))
(call-process "/usr/local/bin/ps2pdf14" nil nil nil ps-file-name pdf-file-name)
(delete-file ps-file-name)
(message "PDF saved to %s" pdf-file-name))))
The existing code written by Rupert Swarbrick, later modified by Rory Yorke, still leaves open the need to specify the file location with a save-as function (e.g. on OSX, this would be ns-write-file-using-panel). Does anyone have a suggestion, please, that adds an option similar to ns-write-file-using-panel and/or perhaps modifies the /tmp directory option written in the script?
Word wrap for Emacs print buffer to PDF
Formatting a header in an Emacs function to print a buffer to PDF w/ line wrapping
(defun harden-newlines ()
(interactive)
"Make all the newlines in the buffer hard."
(save-excursion
(goto-char (point-min))
(while (search-forward "\n" nil t)
(backward-char)
(put-text-property (point) (1+ (point)) 'hard t)
(forward-char))))
;; (defun spool-buffer-given-name (name)
;; (load "ps-print")
;; (let ((tmp ps-left-header))
;; (unwind-protect
;; (progn
;; (setq ps-left-header
;; (list (lambda () name) 'ps-header-dirpart))
;; (ps-spool-buffer-with-faces))
;; (setf ps-left-header tmp))))
(defun spool-buffer-given-name (name)
(let ((ps-left-header (list (format "(%s)" name))))
(ps-spool-buffer-with-faces)))
(defun print-to-pdf ()
"Print the current file to /tmp/print.pdf"
(interactive)
(let ((wbuf (generate-new-buffer "*Wrapped*"))
(sbuf (current-buffer)))
(jit-lock-fontify-now)
(save-current-buffer
(set-buffer wbuf)
(insert-buffer sbuf)
;; (longlines-mode t)
(visual-line-mode t)
(harden-newlines)
(spool-buffer-given-name (buffer-name sbuf))
(kill-buffer wbuf)
(switch-to-buffer "*PostScript*")
(write-file "/tmp/print.ps")
(kill-buffer (current-buffer)))
(call-process "ps2pdf14" nil nil nil
"/tmp/print.ps" "/tmp/print.pdf")
(delete-file "/tmp/print.ps")
(message "PDF saved to /tmp/print.pdf")))
You can modify the last function to take a filename as a parameter:
(defun print-to-pdf (pdf-file-name)
"Print the current file to the given file."
(interactive "FWrite PDF file: ")
(let ((ps-file-name (concat (file-name-sans-extension pdf-file-name) ".ps"))
(wbuf (generate-new-buffer "*Wrapped*"))
(sbuf (current-buffer)))
(jit-lock-fontify-now)
(save-current-buffer
(set-buffer wbuf)
(insert-buffer sbuf)
(setq fill-column 95)
(longlines-mode t)
(harden-newlines)
(message (buffer-name sbuf))
(spool-buffer-given-name (buffer-name sbuf))
(kill-buffer wbuf)
(switch-to-buffer "*PostScript*")
(write-file ps-file-name t)
(kill-buffer (current-buffer)))
(call-process "ps2pdf14" nil nil nil ps-file-name pdf-file-name)
(delete-file ps-file-name)
(message "PDF saved to %s" pdf-file-name)))
You might want to add some code that tests if the PDF file already exist though, to avoid overwriting anything.
I use this function for printing a buffer's content to PDF
(from my .emacs file:)
(defun print-to-pdf ()
(interactive)
(ps-spool-buffer-with-faces)
(switch-to-buffer "*PostScript*")
(write-file "/tmp/tmp.ps")
(kill-buffer "tmp.ps")
(setq cmd (concat "ps2pdf14 /tmp/tmp.ps /home/user/" (buffer-name) ".pdf"))
(shell-command cmd)
(shell-command "rm /tmp/tmp.ps")
(message (concat "Saved to: /home/user/" (buffer-name) ".pdf"))
)
I cannot, however, find a way to enable or apply the visual-line minor mode to the PostScript buffer before it gets written to disk so to enable word wrap in the output.
The problem with getting visual line mode to be respected is that it inserts "soft newlines" (which get ignored by the PS renderer). A solution is to replace these with hard newlines. The code below does what you want, I think. Note that we call harden-newlines in a temporary buffer so as not to mess up the current document. Also, I've changed the output destination to always land in /tmp/print.pdf. It seems... unwise to overwrite documents in your /home without any sort of warning! You can always move the PDF afterwards.
Anyway, here you go. Is this what you wanted?
(defun harden-newlines ()
(interactive)
"Make all the newlines in the buffer hard."
(save-excursion
(goto-char (point-min))
(while (search-forward "\n" nil t)
(backward-char)
(put-text-property (point) (1+ (point)) 'hard t)
(forward-char))))
(defun spool-buffer-given-name (name)
(load "ps-print")
(let ((tmp ps-left-header))
(unwind-protect
(progn
(setq ps-left-header
(list (lambda () name) 'ps-header-dirpart))
(ps-spool-buffer-with-faces))
(setf ps-left-header tmp))))
(defun print-to-pdf ()
"Print the current file to /tmp/print.pdf"
(interactive)
(let ((wbuf (generate-new-buffer "*Wrapped*"))
(sbuf (current-buffer)))
(jit-lock-fontify-now)
(save-current-buffer
(set-buffer wbuf)
(insert-buffer sbuf)
(longlines-mode t)
(harden-newlines)
(spool-buffer-given-name (buffer-name sbuf))
(kill-buffer wbuf)
(switch-to-buffer "*PostScript*")
(write-file "/tmp/print.ps")
(kill-buffer (current-buffer)))
(call-process "ps2pdf14" nil nil nil
"/tmp/print.ps" "/tmp/print.pdf")
(delete-file "/tmp/print.ps")
(message "PDF saved to /tmp/print.pdf")))
Basically I want the *Messages* buffer to always scroll to the bottom when a new message arrives.
Can I do that?
I found auto-revert-tail-mode but that works for buffers that are visiting files.
When I tried it in the Messages buffer, it popped an error:
auto-revert-tail-mode: This buffer is not visiting a file
For multiple frames you probably want:
(defadvice message (after message-tail activate)
"goto point max after a message"
(with-current-buffer "*Messages*"
(goto-char (point-max))
(walk-windows (lambda (window)
(if (string-equal (buffer-name (window-buffer window)) "*Messages*")
(set-window-point window (point-max))))
nil
t)))
Just put point at the end of the buffer M->. If you don't manually move it it will stay there -- IOW, you will always see the tail.
This code seems a bit overkill, but a the simple (goto-char (point-max)) wasn't working for me:
(defadvice message (after message-tail activate)
"goto point max after a message"
(with-current-buffer "*Messages*"
(goto-char (point-max))
(let ((windows (get-buffer-window-list (current-buffer) nil t)))
(while windows
(set-window-point (car windows) (point-max))
(setq windows (cdr windows))))))
Here's an implementation that uses the new advice style.
(defun message-buffer-goto-end-of-buffer (&rest args)
(let* ((win (get-buffer-window "*Messages*"))
(buf (and win (window-buffer win))))
(and win (not (equal (current-buffer) buf))
(set-window-point
win (with-current-buffer buf (point-max))))))
(advice-add 'message :after 'message-buffer-goto-end-of-buffer)
i run 23.3 and there were still way too many occasions where the built-in 'solution' and the orginal defadvice on the message function just didn't cut it, so i wrapped that code in a list / toggle / timer set up and it's working beautifully - no more frustration when debugging!
it's generic, so works on any buffer, although i only really use it for..
(toggle-buffer-tail "*Messages*" "on")
..hope it's useful to someone.
;alist of 'buffer-name / timer' items
(defvar buffer-tail-alist nil)
(defun buffer-tail (name)
"follow buffer tails"
(cond ((or (equal (buffer-name (current-buffer)) name)
(string-match "^ \\*Minibuf.*?\\*$" (buffer-name (current-buffer)))))
((get-buffer name)
(with-current-buffer (get-buffer name)
(goto-char (point-max))
(let ((windows (get-buffer-window-list (current-buffer) nil t)))
(while windows (set-window-point (car windows) (point-max))
(with-selected-window (car windows) (recenter -3)) (setq windows (cdr windows))))))))
(defun toggle-buffer-tail (name &optional force)
"toggle tailing of buffer NAME. when called non-interactively, a FORCE arg of 'on' or 'off' can be used to to ensure a given state for buffer NAME"
(interactive (list (cond ((if name name) (read-from-minibuffer
(concat "buffer name to tail"
(if buffer-tail-alist (concat " (" (caar buffer-tail-alist) ")") "") ": ")
(if buffer-tail-alist (caar buffer-tail-alist)) nil nil
(mapcar '(lambda (x) (car x)) buffer-tail-alist)
(if buffer-tail-alist (caar buffer-tail-alist)))) nil)))
(let ((toggle (cond (force force) ((assoc name buffer-tail-alist) "off") (t "on")) ))
(if (not (or (equal toggle "on") (equal toggle "off")))
(error "invalid 'force' arg. required 'on'/'off'")
(progn
(while (assoc name buffer-tail-alist)
(cancel-timer (cdr (assoc name buffer-tail-alist)))
(setq buffer-tail-alist (remove* name buffer-tail-alist :key 'car :test 'equal)))
(if (equal toggle "on")
(add-to-list 'buffer-tail-alist (cons name (run-at-time t 1 'buffer-tail name))))
(message "toggled 'tail buffer' for '%s' %s" name toggle)))))
edit: changed functionality to display tail at the bottom of the window
Here's an amendment over Peter's / Trey's solutions
(defun modi/messages-auto-tail (&rest _)
"Make *Messages* buffer auto-scroll to the end after each message."
(let* ((buf-name "*Messages*")
;; Create *Messages* buffer if it does not exist
(buf (get-buffer-create buf-name)))
;; Activate this advice only if the point is _not_ in the *Messages* buffer
;; to begin with. This condition is required; otherwise you will not be
;; able to use `isearch' and other stuff within the *Messages* buffer as
;; the point will keep moving to the end of buffer :P
(when (not (string= buf-name (buffer-name)))
;; Go to the end of buffer in all *Messages* buffer windows that are
;; *live* (`get-buffer-window-list' returns a list of only live windows).
(dolist (win (get-buffer-window-list buf-name nil :all-frames))
(with-selected-window win
(goto-char (point-max))))
;; Go to the end of the *Messages* buffer even if it is not in one of
;; the live windows.
(with-current-buffer buf
(goto-char (point-max))))))
(advice-add 'message :after #'modi/messages-auto-tail)