summaryrefslogtreecommitdiff
path: root/tw/home/files/emacs-packages/flymake-guile.el
blob: 2d2a65a0fd995bf3f1766795a99b6f61d2c47035 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
;;; flymake-guile.el --- Flymake checker using `guild compile'  -*- lexical-binding: t -*-
;;; Commentary:
;;; "guild compile" compiles Guile code to bytecode and can output a few basic
;;; warnings.  Let's use this as a linter!
;;; Code:

(defcustom flymake-guile-guild-executable "guild"
  "The guild executable to use.  This will be looked up in $PATH."
  :type '(string)
  :risky t
  :group 'flymake-guile)

(defvar flymake-guile--message-regexp
  (rx bol (group (+? not-newline)) ":"     ; filename
      (group (+ digit)) ":"                ; line
      (group (+ digit)) ": "               ; column
      (group (or "warning" "error")) ": "  ; type
      (group (+ not-newline)) eol)         ; message
  "Regular expression matching messages from guild compile.
`flymake-guile' expects the following capturing groups in this regexp: (1)
file name; (2) line number; (3) column number; (4) error type; (5) message.")

(defvar-local flymake-guile--flymake-proc nil
  "The latest invocation of guild compile.")

(defvar-local flymake-guile--temp-file nil
  "The temporary file name to pass to guild.")

;; See info node: (flymake)An annotated example backend.
(defun flymake-guile (report-fn &rest _args)
  "Run guild compile and report diagnostics from it using REPORT-FN.
Any running invocations are killed before running another one."
  (unless (executable-find flymake-guile-guild-executable)
    (funcall report-fn :panic
             :explanation "Cannot find `flymake-guile-guild-executable' program")
    (error "Cannot find guild executable"))

  (unless flymake-guile--temp-file
    (setq-local
     flymake-guile--temp-file
     (concat "/tmp/flymake-guile-"
             (string-replace
              "/" "!"   ; we don't want to create subdirs under /tmp
              (or (buffer-file-name)
                  (format "temp-%s.scm"
                          (random most-positive-fixnum)))))))

  ;; Kill previous check, if it's still running.
  (when (process-live-p flymake-guile--flymake-proc)
    (kill-process flymake-guile--flymake-proc))

  ;; This needs `lexical-binding'.
  (let ((source (current-buffer)))
    (save-restriction
      (widen)
      ;; Send the buffer to guild on stdin.
      (with-temp-file flymake-guile--temp-file
        (insert-buffer-substring-no-properties source))
      (setq flymake-guile--flymake-proc
            (make-process
             :name "flymake-guild" :noquery t :connection-type 'pipe
             ;; Direct output to a temporary buffer.
             :buffer (generate-new-buffer " *flymake-guile*")
             ;; Guild can't read from stdin; it needs a file.
             :command (list flymake-guile-guild-executable "compile" "-W3" flymake-guile--temp-file)
             :sentinel
             (lambda (proc _event)
               "Parse diagnostic messages once the process PROC has exited."
               ;; Check the process has actually exited, not just been suspended.
               (when (memq (process-status proc) '(exit signal))
                 (unwind-protect
                     ;; Only proceed if we've got the "latest" process.
                     (if (with-current-buffer source (eq proc flymake-guile--flymake-proc))
                         (with-current-buffer (process-buffer proc)
                           (goto-char (point-min))
                           (cl-loop
                            while (search-forward-regexp flymake-guile--message-regexp nil t)
                            for (beg . end) = (flymake-diag-region
                                               (match-string 1)  ; source
                                               (string-to-number (match-string 2))
                                               (string-to-number (match-string 3)))
                            for type = (pcase (match-string 4)
                                         ("warning" :warning)
                                         ("error" :error)
                                         (type (error "Unknown guild error type %s" type)))
                            collect (flymake-make-diagnostic source beg end type (match-string 5))
                            into diags
                            finally (funcall report-fn diags)))
                       (flymake-log :warning "Canceling obsolete check %s" proc))
                   ;; Clean up temporary buffer.
                   (kill-buffer (process-buffer proc))
                   (delete-file flymake-guile--temp-file)))))))))

(provide 'flymake-guile)
;;; flymake-guile.el ends here