branch: externals/auctex-cont-latexmk commit 2762ee8084f202bcadd7dbad6988039b98315bf1 Author: Paul Nelson <ultr...@gmail.com> Commit: Paul Nelson <ultr...@gmail.com>
polish --- README.org | 2 + czm-tex-compile.el | 189 +++++++++++++++++++++++------------------------------ 2 files changed, 85 insertions(+), 106 deletions(-) diff --git a/README.org b/README.org index 17c37c7d9b..089d385feb 100644 --- a/README.org +++ b/README.org @@ -17,4 +17,6 @@ You can tweak underlying the =latexmk= command via =M-x customize-variable czm-t The command =czm-tex-compile-toggle= behaves the way that I prefer -- it enables both =czm-tex-compile-mode= and =flymake-mode=, restricting the backends for the latter to those coming from the former. Depending upon your preferences, you may wish to write your own "wrapper" for =czm-tex-compile-mode= akin to =czm-tex-compile-toggle=. +The way the Flymake backend works, it will update only when the latexmk process reaches a "watching for changes" state and the buffer is unmodified. The workflow to have errors and warnings reported is to save the file and wait a few seconds without editing. + That's all. I prefer this workflow to the alternative in which one compiles the document manually via =TeX-command-master= (=C-c C-c=) and navigates the warning/error messages using =next-error= (=M-n=) and =previous-error= (=M-p=). It also gives a handy way to keep the .aux files up-to-date; I take advantage of this feature in the packages [[https://github.com/ultronozm/czm-preview.el][czm-preview.el]] and [[https://github.com/ultronozm/czm-tex-fold.el][czm-tex-fold.el]] to annotate t [...] diff --git a/czm-tex-compile.el b/czm-tex-compile.el index 72d6db3a0a..11259d5647 100644 --- a/czm-tex-compile.el +++ b/czm-tex-compile.el @@ -3,7 +3,7 @@ ;; Copyright (C) 2023 Paul D. Nelson ;; Author: Paul D. Nelson <nelson.paul.da...@gmail.com> -;; Version: 0.0 +;; Version: 0.1 ;; URL: https://github.com/ultronozm/czm-tex-compile.el ;; Package-Requires: ((emacs "29.1") (auctex)) ;; Keywords: tex @@ -24,9 +24,9 @@ ;;; Commentary: ;; This package provides a minor mode that compiles a LaTeX document -;; via latexmk and reports errors and warnings to a `flymake' backend. -;; Customize the variable `czm-tex-compile-command' to change the -;; command used to compile the document. +;; via latexmk, reporting errors via `flymake'. Customize the +;; variable `czm-tex-compile-command' to change the command used to +;; compile the document. ;; ;; My use-package declaration: ;; @@ -49,83 +49,90 @@ "Command to compile LaTeX documents." :type 'string) -(defvar-local czm-tex-compile-process nil +(defvar-local czm-tex-compile--process nil "Process running the LaTeX compilation.") -(defvar-local czm-tex-compile-log-watch-descriptor nil) -(defvar-local czm-tex-compile-report-fn nil) -(defvar-local czm-tex-compile-log-watch-timer nil) -(defvar-local czm-tex-compile--compilation-buffer-name nil) +(defvar-local czm-tex-compile--report-fn nil + "Function provided by Flymake for reporting diagnostics.") + +(defvar-local czm-tex-compile--log-watch-timer nil + "Timer for reporting changes to the log file.") + +(defvar-local czm-tex-compile--compilation-buffer-name nil + "Name of the buffer used for LaTeX compilation.") ;;;###autoload (define-minor-mode czm-tex-compile-mode "If enabled, run LaTeX compilation on the current buffer." - :lighter " TexC" + :lighter nil (if czm-tex-compile-mode (let ((name (and (string-match "\\([^\.]+\\)\.tex" (buffer-name)) (match-string 1 (buffer-name))))) (unless name (user-error "Buffer name does not match expected pattern")) - (when (process-live-p czm-tex-compile-process) - (interrupt-process czm-tex-compile-process) + (when (process-live-p czm-tex-compile--process) + (interrupt-process czm-tex-compile--process) (sit-for 0.1) - (delete-process czm-tex-compile-process)) - (setq czm-tex-compile--compilation-buffer-name (concat "*czm-tex-compile-" name "*")) + (delete-process czm-tex-compile--process)) + (setq czm-tex-compile--compilation-buffer-name (concat "*czm-tex-compile-" (expand-file-name name) + "*")) (let ((command (concat czm-tex-compile-command " " name ".tex"))) - (setq czm-tex-compile-process + (setq czm-tex-compile--process (start-process-shell-command "czm-tex-compile" czm-tex-compile--compilation-buffer-name command))) (add-hook 'kill-buffer-hook 'czm-tex-compile--kill-process nil t) - (add-hook 'flymake-diagnostic-functions #'czm-tex-compile-flymake nil t)) + (add-hook 'flymake-diagnostic-functions #'czm-tex-compile-flymake nil t) + (when czm-tex-compile--log-watch-timer + (cancel-timer czm-tex-compile--log-watch-timer) + (setq czm-tex-compile--log-watch-timer nil)) + (setq czm-tex-compile--log-watch-timer + (run-with-timer 1 1 #'czm-tex-compile-report-if-fresh))) (czm-tex-compile--kill-process) - (when czm-tex-compile-log-watch-descriptor - (file-notify-rm-watch czm-tex-compile-log-watch-descriptor) - (setq czm-tex-compile-log-watch-descriptor nil)) - (when czm-tex-compile-log-watch-timer - (cancel-timer czm-tex-compile-log-watch-timer) - (setq czm-tex-compile-log-watch-timer nil)) - (when czm-tex-compile-report-fn - (setq czm-tex-compile-report-fn nil)))) + (when czm-tex-compile--report-fn + (setq czm-tex-compile--report-fn nil)))) + +(defvar-local czm-tex-compile--old-flymake-diagnostic-functions nil + "Value of `flymake-diagnostic-functions' before calling `czm-tex-compile-toggle'.") ;;;###autoload (defun czm-tex-compile-toggle () - "Toggle `czm-tex-compile-mode', and also `flymake'." + "Toggle `czm-tex-compile-mode', and also `flymake-mode'." (interactive) (if czm-tex-compile-mode (progn (czm-tex-compile-mode 0) (flymake-mode 0) - (czm-tex-compile-relax-flymake-backends) + (setq-local flymake-diagnostic-functions + czm-tex-compile--old-flymake-diagnostic-functions) (message "czm-tex-compile-mode and flymake-mode disabled")) (czm-tex-compile-mode 1) - (czm-tex-compile-restrict-flymake-backends) + (setq czm-tex-compile--old-flymake-diagnostic-functions flymake-diagnostic-functions) + (setq-local flymake-diagnostic-functions '(czm-tex-compile-flymake t)) (flymake-mode 1) (message "czm-tex-compile-mode and flymake-mode enabled"))) (defun czm-tex-compile--kill-process () - "Kill the LaTeX compilation process associated with the buffer." - (when (process-live-p czm-tex-compile-process) - (interrupt-process czm-tex-compile-process) + "Kill the LaTeX compilation process associated with the buffer. +Also kill the timer for watching the log file." + (when (process-live-p czm-tex-compile--process) + (interrupt-process czm-tex-compile--process) (sit-for 0.1) - (delete-process czm-tex-compile-process)) + (delete-process czm-tex-compile--process)) (when (get-buffer czm-tex-compile--compilation-buffer-name) - (kill-buffer czm-tex-compile--compilation-buffer-name))) + (kill-buffer czm-tex-compile--compilation-buffer-name)) + (when czm-tex-compile--log-watch-timer + (cancel-timer czm-tex-compile--log-watch-timer) + (setq czm-tex-compile--log-watch-timer nil))) (require 'tex) - -(defun testing () - (interactive) - (while (re-search-forward "Warning:" nil t) - ;; make it all appear on one line - (end-of-line) - (while (not (looking-at "\n\n")) - (delete-char 1) - (end-of-line)))) - (defun czm-tex-compile-process-log () - "Process the log file for the current LaTeX document." + "Process log file for current LaTeX document. +Returns a list of triples (ERROR-P DESCRIPTION REGION), where +ERROR-P is non-nil if the error is an error rather than a +warning, DESCRIPTION is what you'd expect, and REGION is a cons +cell (BEG . END) indicating where the error happens." (let* ((current-buf (current-buffer)) (tex-file (buffer-file-name)) (log-file (concat (file-name-sans-extension tex-file) @@ -152,7 +159,8 @@ (lambda (item) (let* ((error-p (eq (nth 0 item) 'error)) - (description-raw (nth (if error-p 3 5) item)) + (description-raw (nth (if error-p 3 5) + item)) (description (if error-p description-raw (substring description-raw 0 @@ -198,23 +206,8 @@ filtered))) stuff)))) -(defun czm-tex-compile--fresh-p () - "Return non-nil if logged errors should apply to current buffer. -This is the case if the current buffer is not modified, the -current buffer is a file, the current buffer has a log file, and -the log file is newer than the current buffer." - (when-let* ((file (buffer-file-name)) - (log-file (concat (file-name-sans-extension file) - ".log"))) - (and - (not (buffer-modified-p)) - (file-exists-p file) - (file-exists-p log-file) - (time-less-p (nth 5 (file-attributes file)) - (nth 5 (file-attributes log-file)))))) - (defun czm-tex-compile-report (report-fn) - "Call REPORT-FN if the current buffer is fresh." + "Report errors from log file to flymake backend REPORT-FN." (let* ((log-data (czm-tex-compile-process-log)) (diags (mapcar (lambda (datum) @@ -232,59 +225,43 @@ the log file is newer than the current buffer." (funcall report-fn diags) t)) +(defconst czm-tex-compile--watching-str "=== Watching for updated files. Use ctrl/C to stop ..." + "String indicating that latexmk is watching for updated files.") + +(defun czm-tex-compile--fresh-p () + "Return non-nil if logged errors should apply to current buffer. +This is the case if the current buffer is not modified, the +current buffer is a file, the current buffer has a log file, the +log file is newer than the current buffer, and the current +latexmk compilation is in a \"Watching\" state." + (when-let* ((file (buffer-file-name)) + (log-file (concat (file-name-sans-extension file) + ".log"))) + (and + (when-let ((buf (get-buffer czm-tex-compile--compilation-buffer-name))) + (with-current-buffer buf + (goto-char (point-max)) + (forward-line -1) + (equal (buffer-substring (point) (line-end-position)) + czm-tex-compile--watching-str))) + (not (buffer-modified-p)) + (file-exists-p file) + (file-exists-p log-file) + (time-less-p (nth 5 (file-attributes file)) + (nth 5 (file-attributes log-file)))))) + (defun czm-tex-compile-report-if-fresh () "Call REPORT-FN if the current buffer is fresh." - (when (and czm-tex-compile-report-fn + (when (and czm-tex-compile--report-fn (czm-tex-compile--fresh-p)) - (czm-tex-compile-report czm-tex-compile-report-fn))) - -(defun czm-tex-compile-log-timer-fn () - "Call `czm-tex-compile-report-if-fresh' and cancels the timer." - (when czm-tex-compile-log-watch-timer - (cancel-timer czm-tex-compile-log-watch-timer)) - (czm-tex-compile-report-if-fresh)) - -(defun czm-tex-compile-log-change-handler (event) - "Handle EVENT from log watcher. -If EVENT is `changed', then run `czm-tex-compile-log-timer-fn' -one second from now, so that the log has enough time to fully -update." - (when (eq (nth 1 event) - 'changed) - (when czm-tex-compile-log-watch-timer - (cancel-timer czm-tex-compile-log-watch-timer)) - (setq czm-tex-compile-log-watch-timer - (run-with-timer 1 1 #'czm-tex-compile-log-timer-fn)))) - -(require 'filenotify) + (czm-tex-compile-report czm-tex-compile--report-fn))) (defun czm-tex-compile-flymake (report-fn &rest _args) "Flymake backend for LaTeX based on latexmk. -REPORT-FN is the function called to report diagnostics. -ARGS are the keyword-value pairs concerning edits" +Save REPORT-FN in a local variable, called by +`czm-tex-compile--log-watch-timer' to report diagnostics." (when (czm-tex-compile-mode) - (setq czm-tex-compile-report-fn report-fn) - (czm-tex-compile-report-if-fresh) - (when czm-tex-compile-log-watch-descriptor - (file-notify-rm-watch czm-tex-compile-log-watch-descriptor)) - (setq czm-tex-compile-log-watch-descriptor - (file-notify-add-watch - (concat (file-name-sans-extension (buffer-file-name)) - ".log") - '(change) - #'czm-tex-compile-log-change-handler)))) - -(defun czm-tex-compile-restrict-flymake-backends () - "Restrict flymake backends to `czm-tex-compile-flymake'." - (interactive) - (setq-local flymake-diagnostic-functions '(czm-tex-compile-flymake t))) - -(defun czm-tex-compile-relax-flymake-backends () - "Relax flymake backends to include `LaTeX-flymake'." - (interactive) - (setq-local flymake-diagnostic-functions '(czm-tex-compile-flymake LaTeX-flymake t))) - - + (setq czm-tex-compile--report-fn report-fn))) (provide 'czm-tex-compile) ;;; czm-tex-compile.el ends here