config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

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