ledger-flymake.el (6786B)
1 ;;; ledger-flymake.el --- A ledger Flymake backend -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2018 J. Alexander Branham (alex DOT branham AT gmail DOT com) 4 5 ;; This file is not part of GNU Emacs. 6 7 ;; This is free software; you can redistribute it and/or modify it under 8 ;; the terms of the GNU General Public License as published by the Free 9 ;; Software Foundation; either version 3, or (at your option) any later 10 ;; version. 11 ;; 12 ;; This is distributed in the hope that it will be useful, but WITHOUT 13 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 ;; for more details. 16 ;; 17 ;; You should have received a copy of the GNU General Public License 18 ;; along with GNU Emacs; see the file COPYING. If not, write to the 19 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 20 ;; MA 02110-1301 USA. 21 22 ;;; Commentary: 23 ;; Flymake is the built-in Emacs package to support on-the-fly syntax checking. 24 ;; This file adds support for flymake to `ledger-mode'. Enable it by calling 25 ;; `ledger-flymake-enable' from a file-visiting ledger buffer. To enable it 26 ;; automatically, put this in your .emacs: 27 ;; 28 ;; (add-hook 'ledger-mode-hook #'ledger-flymake-enable) 29 30 ;;; Code: 31 (require 'cl-lib) 32 (require 'flymake) 33 (require 'ledger-exec) ; for `ledger-binary-path' 34 (require 'ledger-report) ; for `ledger-master-file' 35 36 ;; To silence byte compiler warnings in Emacs 25 and older: 37 (declare-function flymake-diag-region "flymake" (buffer line &optional col)) 38 (declare-function flymake-make-diagnostic "flymake" (buffer beg end type text &optional data overlay-properties)) 39 40 (defvar-local ledger--flymake-proc nil) 41 42 (defcustom ledger-flymake-be-pedantic nil 43 "If non-nil, pass the --pedantic flag for ledger to the flymake backend. 44 If --pedantic is in your ledgerrc file, then --pedantic gets 45 passed regardless of the value." 46 :type 'boolean 47 :package-version '(ledger-mode . "4.0.0") 48 :group 'ledger) 49 50 (defcustom ledger-flymake-be-explicit nil 51 "If non-nil, pass the --explicit flag for ledger to the flymake backend. 52 If --explicit is in your ledgerrc file, then --explicit gets 53 passed regardless of the value." 54 :type 'boolean 55 :package-version '(ledger-mode . "4.0.0") 56 :group 'ledger) 57 58 ;; Based on the example from Flymake's info: 59 (defun ledger-flymake (report-fn &rest _args) 60 "A Flymake backend for `ledger-mode'. 61 62 Flymake calls this with REPORT-FN as needed." 63 (unless (executable-find ledger-binary-path) 64 (error "Cannot find ledger")) 65 ;; If a live process launched in an earlier check was found, that 66 ;; process is killed. When that process's sentinel eventually runs, 67 ;; it will notice its obsoletion, since it have since reset 68 ;; `ledger-flymake-proc' to a different value 69 (when (process-live-p ledger--flymake-proc) 70 (kill-process ledger--flymake-proc)) 71 ;; Save the current buffer, the narrowing restriction, remove any 72 ;; narrowing restriction. 73 (let* ((source (current-buffer)) 74 (file (or (ledger-master-file) (buffer-file-name)))) 75 (save-restriction 76 (widen) 77 ;; Reset the `ledger--flymake-proc' process to a new process 78 ;; calling the ledger tool. 79 (setq 80 ledger--flymake-proc 81 (make-process 82 :name "ledger-flymake" :noquery t :connection-type 'pipe 83 :buffer (generate-new-buffer " *ledger-flymake*") 84 :command (cl-remove 85 nil 86 `(,ledger-binary-path "-f" ,file 87 ,(when ledger-flymake-be-pedantic "--pedantic") 88 ,(when ledger-flymake-be-explicit "--explicit") 89 "balance")) 90 :sentinel 91 (lambda (proc _event) 92 ;; Check that the process has indeed exited, as it might 93 ;; be simply suspended. 94 (when (eq 'exit (process-status proc)) 95 (unwind-protect 96 ;; Only proceed if `proc' is the same as 97 ;; `ledger--flymake-proc', which indicates that 98 ;; `proc' is not an obsolete process. 99 (if (with-current-buffer source (eq proc ledger--flymake-proc)) 100 (with-current-buffer (process-buffer proc) 101 (goto-char (point-min)) 102 ;; Parse the output buffer for diagnostic's 103 ;; messages and locations, collect them in a list 104 ;; of objects, and call `report-fn'. 105 (cl-loop 106 while (search-forward-regexp 107 ;; This regex needs to match the whole error. We 108 ;; also need a capture group for the error message 109 ;; (that's group 1 here) and the line number 110 ;; (group 2). 111 (rx line-start "While parsing file \"" (one-or-more (not whitespace)) " line " (group-n 2 (one-or-more num)) ":\n" 112 (zero-or-more line-start "While " (one-or-more not-newline) "\n" ) 113 (minimal-match (zero-or-more line-start (zero-or-more not-newline) "\n")) 114 (group-n 1 "Error: " (one-or-more not-newline) "\n")) 115 nil t) 116 for msg = (match-string 1) 117 for (beg . end) = (flymake-diag-region 118 source 119 (string-to-number (match-string 2))) 120 for type = :error 121 collect (flymake-make-diagnostic source 122 beg 123 end 124 type 125 msg) 126 into diags 127 finally (funcall report-fn diags))) 128 (flymake-log :warning "Canceling obsolete check %s" 129 proc)) 130 ;; Cleanup the temporary buffer used to hold the 131 ;; check's output. 132 (kill-buffer (process-buffer proc)))))))))) 133 134 ;;;###autoload 135 (defun ledger-flymake-enable () 136 "Enable `flymake-mode' in `ledger-mode' buffers." 137 (unless (> emacs-major-version 25) 138 (error "Ledger-flymake requires Emacs version 26 or higher")) 139 ;; Add `ledger-flymake' to `flymake-diagnostic-functions' so that flymake can 140 ;; work in ledger-mode: 141 (add-hook 'flymake-diagnostic-functions 'ledger-flymake nil t) 142 (flymake-mode)) 143 144 (provide 'ledger-flymake) 145 146 ;;; ledger-flymake.el ends here