aboutsummaryrefslogtreecommitdiff
path: root/modules/flycheck-config.el
blob: 5626095c5748fa605e30beecec33fb499805095a (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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
;;; flycheck-config --- Syntax/Grammar Check -*- lexical-binding: t; coding: utf-8; -*-
;;  author Craig Jennings <c@cjennings.net>

;;; Commentary:
;;
;; Layer: 2 (Core UX).
;; Category: C/P.
;; Load shape: eager.
;; Eager reason: general linting setup; spec target is hook-loaded, a deferral
;;   candidate.
;; Top-level side effects: package configuration via use-package, binds into
;;   cj/custom-keymap through use-package :map.
;; Runtime requires: keybindings.
;; Direct test load: yes (requires keybindings explicitly).
;;
;; This file configures Flycheck for on-demand syntax and grammar checking.
;; - Flycheck starts automatically only in sh-mode and emacs-lisp-mode

;; - This binds a custom helper (=cj/flycheck-list-errors=) to "C-; ?"
;;   for popping up Flycheck's error list in another window.

;; - It also customizes Checkdoc to suppress only the "sentence-end-double-space"
;;   and "warn-escape" warnings.

;; - It registers LanguageTool for comprehensive grammar checking of prose files
;;   (text-mode, markdown-mode, gfm-mode, org-mode).

;; Note: Grammar checking is on-demand only to avoid performance issues.
;; Hitting "C-; ?" runs cj/flycheck-prose-on-demand if in an org buffer.

;; The cj/flycheck-prose-on-demand function:
;; - Turns on flycheck for the local buffer
;; - Enables LanguageTool checker
;; - Triggers an immediate check
;; - Displays errors in the *Flycheck errors* buffer

;; Installation:
;; On Arch Linux:
;;   sudo pacman -S languagetool
;;
;; The wrapper script at scripts/languagetool-flycheck formats LanguageTool's
;; JSON output into flycheck-compatible format.  It requires Python 3.

;;; Code:

(require 'keybindings) ;; provides cj/custom-keymap (use-package :map below)

(defun cj/prose-helpers-on ()
  "Ensure that `abbrev-mode' and `flycheck-mode' are on in the current buffer."
  (interactive)
  (unless (bound-and-true-p abbrev-mode)
    (abbrev-mode 1))
  (unless (bound-and-true-p flycheck-mode)
    (flycheck-mode 1)))

;; ---------------------------------- Linting ----------------------------------

(use-package flycheck
  :defer t
  :commands (flycheck-list-errors
             cj/flycheck-list-errors)
  :hook ((sh-mode emacs-lisp-mode) . flycheck-mode)
  :bind
   (:map cj/custom-keymap
			  ("?" . cj/flycheck-list-errors))
  :custom
  ;; Only disable these two Checkdoc warnings; leave all others intact.
  (checkdoc-arguments
   '(("sentence-end-double-space" nil)
     ("warn-escape"               nil)))
  ;; Modeline customization (rendered via mode-line-format in modeline-config.el).
  ;; The count portion picks up `error' / `warning' faces because
  ;; `flycheck-mode-line-color' stays t (the default).
  (flycheck-mode-line-prefix "🐛")
  (flycheck-mode-success-indicator " ✓")
  :config

  ;; use the load-path of the currently running Emacs instance
  (setq flycheck-emacs-lisp-load-path 'inherit)

  ;; Define LanguageTool checker for comprehensive grammar checking.
  ;; The :command executable must be a string literal at macro-expansion
  ;; time (flycheck rejects `(eval ...)' in the first position), so we
  ;; backquote-splice the expanded path into the form and eval it
  ;; explicitly.  Survives a non-standard `user-emacs-directory'.
  (eval
   `(flycheck-define-checker languagetool
      "A grammar checker using LanguageTool.
Uses a wrapper script to format output for flycheck."
      :command (,(expand-file-name "scripts/languagetool-flycheck"
                                   user-emacs-directory)
                source-inplace)
      :error-patterns
      ((warning line-start (file-name) ":" line ":" column ": "
                (message) line-end))
      :modes (text-mode markdown-mode gfm-mode org-mode)))
  (add-to-list 'flycheck-checkers 'languagetool)

  (defun cj/flycheck-list-errors ()
    "Display flycheck's error list and switch to its buffer.
Runs flycheck-prose-on-demand if in an org-buffer."
    (interactive)
    (when (derived-mode-p 'org-mode)
      (cj/flycheck-prose-on-demand))

    (flycheck-list-errors)
    (switch-to-buffer-other-window "*Flycheck errors*"))

  (defun cj/flycheck-prose-on-demand ()
    "Enable Flycheck with LanguageTool in this buffer, run it, and show errors."
    (interactive)
    ;; turn on Flycheck locally
    (flycheck-mode 1)
    ;; ensure LanguageTool is valid for current mode
    (flycheck-add-mode 'languagetool major-mode)
    ;; select LanguageTool as the checker
    (setq-local flycheck-checker 'languagetool)
    ;; trigger immediate check
    (flycheck-buffer)))

(with-eval-after-load 'which-key
  (which-key-add-key-based-replacements "C-; ?" "list errors"))

(provide 'flycheck-config)
;;; flycheck-config.el ends here