.
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | EMACS ?= emacs
2 | EASK ?= eask
3 |
4 | .PHONY: clean checkdoc lint package install compile test
5 |
6 | ci: clean package install compile
7 |
8 | package:
9 | @echo "Packaging..."
10 | $(EASK) package
11 |
12 | install:
13 | @echo "Installing..."
14 | $(EASK) install
15 |
16 | compile:
17 | @echo "Compiling..."
18 | $(EASK) compile
19 |
20 | test:
21 | @echo "Testing..."
22 | $(EASK) test ert ./test/*.el
23 |
24 | checkdoc:
25 | @echo "Run checkdoc..."
26 | $(EASK) lint checkdoc
27 |
28 | lint:
29 | @echo "Run package-lint..."
30 | $(EASK) lint package
31 |
32 | clean:
33 | $(EASK) clean all
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.gnu.org/licenses/gpl-3.0)
2 | [](https://melpa.org/#/flymake-grammarly)
3 | [](https://stable.melpa.org/#/flymake-grammarly)
4 |
5 |
6 |
7 | # flymake-grammarly
8 | > Flymake support for Grammarly.
9 |
10 | [](https://github.com/emacs-grammarly/flymake-grammarly/actions/workflows/test.yml)
11 |
12 |
13 |
14 |
15 |
16 | ## 🔧 Usage
17 |
18 | To enable this package, simply add loading to your config like the code below.
19 |
20 | ```el
21 | (require 'flymake-grammarly)
22 | ```
23 |
24 | You can generally call `flymake-grammarly-load` to any `mode-hook` for
25 | grammarly to start working.
26 |
27 | ```el
28 | (add-hook 'text-mode-hook 'flymake-grammarly-load)
29 | (add-hook 'latex-mode-hook 'flymake-grammarly-load)
30 | (add-hook 'org-mode-hook 'flymake-grammarly-load)
31 | (add-hook 'markdown-mode-hook 'flymake-grammarly-load)
32 | ```
33 |
34 | otherwise you can call `flymake-grammarly-maybe-load` like the snippt below.
35 |
36 | ```el
37 | (add-hook 'find-file-hook 'flymake-grammarly-maybe-load)
38 | ```
39 |
40 | If you encounter the performance issue, try raise `flymake-grammarly-check-time` higher.
41 | The request will be send by this time everytime the buffer has changed.
42 |
43 | ```el
44 | (setq flymake-grammarly-check-time 0.8)
45 | ```
46 |
47 | ## 📝 Todo List
48 |
49 | - [ ] Strip only text data, if other data like `# header` or `> quote` will return nothing.
50 | - [ ] Usable but not fast enough, really depends on Grammarly's analyzer.
51 |
52 | ## 🛠️ Contribute
53 |
54 | [](http://makeapullrequest.com)
55 | [](https://github.com/bbatsov/emacs-lisp-style-guide)
56 | [](https://www.paypal.me/jcs090218)
57 | [](https://www.patreon.com/jcs090218)
58 |
59 | If you would like to contribute to this project, you may either
60 | clone and make pull requests to this repository. Or you can
61 | clone the project and establish your own branch of this tool.
62 | Any methods are welcome!
63 |
64 | ### 🔬 Development
65 |
66 | To run the test locally, you will need the following tools:
67 |
68 | - [Eask](https://emacs-eask.github.io/)
69 | - [Make](https://www.gnu.org/software/make/) (optional)
70 |
71 | Install all dependencies and development dependencies:
72 |
73 | ```sh
74 | eask install-deps --dev
75 | ```
76 |
77 | To test the package's installation:
78 |
79 | ```sh
80 | eask package
81 | eask install
82 | ```
83 |
84 | To test compilation:
85 |
86 | ```sh
87 | eask compile
88 | ```
89 |
90 | **🪧 The following steps are optional, but we recommend you follow these lint results!**
91 |
92 | The built-in `checkdoc` linter:
93 |
94 | ```sh
95 | eask lint checkdoc
96 | ```
97 |
98 | The standard `package` linter:
99 |
100 | ```sh
101 | eask lint package
102 | ```
103 |
104 | *📝 P.S. For more information, find the Eask manual at https://emacs-eask.github.io/.*
105 |
106 | ## ⚜️ License
107 |
108 | This program is free software; you can redistribute it and/or modify
109 | it under the terms of the GNU General Public License as published by
110 | the Free Software Foundation, either version 3 of the License, or
111 | (at your option) any later version.
112 |
113 | This program is distributed in the hope that it will be useful,
114 | but WITHOUT ANY WARRANTY; without even the implied warranty of
115 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
116 | GNU General Public License for more details.
117 |
118 | You should have received a copy of the GNU General Public License
119 | along with this program. If not, see .
120 |
121 | See [`LICENSE`](./LICENSE.txt) for details.
122 |
--------------------------------------------------------------------------------
/etc/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emacs-grammarly/flymake-grammarly/41b4e25f45352a24257105eae3003b1fc236ed7b/etc/logo.png
--------------------------------------------------------------------------------
/etc/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emacs-grammarly/flymake-grammarly/41b4e25f45352a24257105eae3003b1fc236ed7b/etc/screenshot.png
--------------------------------------------------------------------------------
/flymake-grammarly.el:
--------------------------------------------------------------------------------
1 | ;;; flymake-grammarly.el --- Flymake support for Grammarly -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2020-2025 Shen, Jen-Chieh
4 | ;; Created date 2020-08-23 17:21:13
5 |
6 | ;; Author: Shen, Jen-Chieh
7 | ;; URL: https://github.com/emacs-grammarly/flymake-grammarly
8 | ;; Version: 0.2.1
9 | ;; Package-Requires: ((emacs "26.1") (grammarly "0.3.0") (s "1.12.0"))
10 | ;; Keywords: convenience grammar check
11 |
12 | ;; This file is NOT part of GNU Emacs.
13 |
14 | ;; This program is free software; you can redistribute it and/or modify
15 | ;; it under the terms of the GNU General Public License as published by
16 | ;; the Free Software Foundation, either version 3 of the License, or
17 | ;; (at your option) any later version.
18 |
19 | ;; This program is distributed in the hope that it will be useful,
20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 | ;; GNU General Public License for more details.
23 |
24 | ;; You should have received a copy of the GNU General Public License
25 | ;; along with this program. If not, see .
26 |
27 | ;;; Commentary:
28 | ;;
29 | ;; Flymake support for Grammarly.
30 | ;;
31 |
32 | ;;; Code:
33 |
34 | (require 'cl-lib)
35 | (require 'json)
36 | (require 'dom)
37 |
38 | (require 'flymake)
39 | (require 'grammarly)
40 | (require 's)
41 |
42 | (defgroup flymake-grammarly nil
43 | "Flymake support for Grammarly."
44 | :prefix "flymake-grammarly-"
45 | :group 'flymake
46 | :link '(url-link :tag "Github" "https://github.com/emacs-grammarly/flymake-grammarly"))
47 |
48 | (defcustom flymake-grammarly-active-modes
49 | '(text-mode latex-mode org-mode markdown-mode)
50 | "List of mode will enable grammarly in the buffer."
51 | :type 'list
52 | :group 'flymake-grammarly)
53 |
54 | (defcustom flymake-grammarly-check-time 0.8
55 | "How long do we call request after we done typing."
56 | :type 'float
57 | :group 'flymake-grammarly)
58 |
59 | (defconst flymake-grammarly--avoidance-rule
60 | '((":" . "\n"))
61 | "Replace character to another character to avoid from Grammarly API.")
62 |
63 | (defvar flymake-grammarly--show-debug-message nil
64 | "Show the debug message from this package.")
65 |
66 | (defvar-local flymake-grammarly--done-checking nil
67 | "Check if Grammarly API done checking.")
68 |
69 | (defvar-local flymake-grammarly--point-data nil
70 | "List of error/warning JSON data.")
71 |
72 | (defvar-local flymake-grammarly--last-buffer-string nil
73 | "Record the last buffer string.")
74 |
75 | (defvar-local flymake-grammarly--request-timer nil
76 | "Timer that will tell to do the request.")
77 |
78 | ;;; Util
79 |
80 | (defun flymake-grammarly--column-at-pos (&optional pt)
81 | "Column at PT."
82 | (unless pt (setq pt (point)))
83 | (save-excursion (goto-char pt) (current-column)))
84 |
85 | (defun flymake-grammarly--debug-message (fmt &rest args)
86 | "Debug message like function `message' with same argument FMT and ARGS."
87 | (when flymake-grammarly--show-debug-message
88 | (apply #'message fmt args)))
89 |
90 | ;;; Grammarly
91 |
92 | (defun flymake-grammarly--on-open ()
93 | "On open Grammarly API."
94 | (when flymake-mode
95 | (flymake-grammarly--debug-message "[INFO] Start connecting to Grammarly API...")))
96 |
97 | (defun flymake-grammarly--on-message (data)
98 | "Received DATA from Grammarly API."
99 | (when flymake-mode
100 | (flymake-grammarly--debug-message
101 | "[INFO] Receiving data from grammarly, level (%s) : %s"
102 | (length flymake-grammarly--point-data) data)
103 | (when (string-match-p "\"highlightBegin\":" data)
104 | (push data flymake-grammarly--point-data))))
105 |
106 | (defun flymake-grammarly--on-close ()
107 | "On close Grammarly API."
108 | (when flymake-mode
109 | (setq flymake-grammarly--done-checking t)
110 | (flymake-mode 1)))
111 |
112 | (add-to-list 'grammarly-on-open-function-list 'flymake-grammarly--on-open)
113 | (add-to-list 'grammarly-on-message-function-list 'flymake-grammarly--on-message)
114 | (add-to-list 'grammarly-on-close-function-list 'flymake-grammarly--on-close)
115 |
116 | ;;; Core
117 |
118 | (defun flymake-grammarly--minified-string (str)
119 | "Minify the STR to check if any text changed."
120 | (declare (side-effect-free t))
121 | (md5 (replace-regexp-in-string "[[:space:]\n]+" " " str)))
122 |
123 | (defun flymake-grammarly--kill-timer ()
124 | "Kill the timer."
125 | (when (timerp flymake-grammarly--request-timer)
126 | (cancel-timer flymake-grammarly--request-timer)
127 | (setq flymake-grammarly--request-timer nil)))
128 |
129 | (defun flymake-grammarly--reset-request ()
130 | "Reset some variables so the next time the user done typing can reuse."
131 | (flymake-grammarly--debug-message "[INFO] Reset grammarly requests!")
132 | (setq flymake-grammarly--last-buffer-string (buffer-string)
133 | flymake-grammarly--point-data nil
134 | flymake-grammarly--done-checking nil))
135 |
136 | (defun flymake-grammarly--after-change-functions (&rest _)
137 | "After change function to check if content change."
138 | (unless (string=
139 | (flymake-grammarly--minified-string flymake-grammarly--last-buffer-string)
140 | (flymake-grammarly--minified-string (buffer-string)))
141 | (flymake-grammarly--kill-timer)
142 | (setq flymake-grammarly--request-timer
143 | (run-with-idle-timer flymake-grammarly-check-time nil
144 | 'flymake-grammarly--reset-request))))
145 |
146 | (defun flymake-grammarly--encode-char (char-code)
147 | "Turn CHAR-CODE to character string."
148 | (cl-case char-code
149 | (4194208 (cons " " 2))
150 | (4194201 (cons "'" 3))))
151 |
152 | (defun flymake-grammarly--html-to-text (html)
153 | "Turn HTML to text."
154 | (with-temp-buffer
155 | (insert html)
156 | (goto-char (point-min))
157 | (while (not (eobp))
158 | (let ((replace-data (flymake-grammarly--encode-char (char-before))))
159 | (when replace-data
160 | (backward-delete-char (cdr replace-data))
161 | (insert (car replace-data))))
162 | (forward-char 1))
163 | (dom-texts (libxml-parse-html-region (point-min) (point-max)))))
164 |
165 | (defun flymake-grammarly--grab-info (data attr)
166 | "Grab value through ATTR key with DATA."
167 | (let* ((attrs (split-string attr " "))
168 | (json-object-type 'hash-table)
169 | (json-array-type 'list)
170 | (json-key-type 'string)
171 | (target-val (json-read-from-string data)))
172 | (while (< 0 (length attrs))
173 | (setq target-val (gethash (nth 0 attrs) target-val))
174 | (pop attrs))
175 | target-val))
176 |
177 | (defun flymake-grammarly--valid-description (desc)
178 | "Convert DESC to valid description."
179 | (setq desc (replace-regexp-in-string "\n" "" desc)
180 | desc (replace-regexp-in-string "[ ]+" " " desc))
181 | desc)
182 |
183 | (defun flymake-grammarly--check-all (source-buffer)
184 | "Check grammar for SOURCE-BUFFER document."
185 | (let (check-list)
186 | (dolist (data flymake-grammarly--point-data)
187 | (let* ((offset (point-min)) ; narrowed buffer
188 | (pt-beg (+ offset (flymake-grammarly--grab-info data "highlightBegin")))
189 | (pt-end (+ offset (flymake-grammarly--grab-info data "highlightEnd")))
190 | (exp (flymake-grammarly--grab-info data "explanation"))
191 | (card-desc (unless exp (flymake-grammarly--grab-info data "cardLayout groupDescription")))
192 | (desc (flymake-grammarly--html-to-text (or exp card-desc "")))
193 | (type (if exp (if (string-match-p "error" data) :error :warning) :warning)))
194 | (setq desc (flymake-grammarly--valid-description desc))
195 | (push (flymake-make-diagnostic source-buffer pt-beg pt-end type desc) check-list)))
196 | check-list))
197 |
198 | (defun flymake-grammarly--apply-avoidance-rule (str)
199 | "Apply avoidance rule to STR."
200 | (dolist (rule flymake-grammarly--avoidance-rule)
201 | (setq str (s-replace (car rule) (cdr rule) str)))
202 | str)
203 |
204 | (defun flymake-grammarly--grammar-check ()
205 | "Grammar check once."
206 | (unless flymake-grammarly--done-checking
207 | (flymake-grammarly--reset-request)
208 | (grammarly-check-text (flymake-grammarly--apply-avoidance-rule (buffer-string)))))
209 |
210 | ;;; Flymake
211 |
212 | (defvar flymake-grammarly--report-fnc nil
213 | "Record report function/execution.")
214 |
215 | (defvar flymake-grammarly--source-buffer nil
216 | "Record source check buffer.")
217 |
218 | (defun flymake-grammarly--report-once ()
219 | "Report with flymake after done requesting."
220 | (when (functionp flymake-grammarly--report-fnc)
221 | (flymake-grammarly--grammar-check)
222 | (funcall flymake-grammarly--report-fnc
223 | (flymake-grammarly--check-all flymake-grammarly--source-buffer))))
224 |
225 | (defun flymake-grammarly--checker (report-fn &rest _args)
226 | "Diagnostic checker function with REPORT-FN."
227 | (setq flymake-grammarly--report-fnc report-fn
228 | flymake-grammarly--source-buffer (current-buffer))
229 | (flymake-grammarly--report-once))
230 |
231 | ;;; Entry
232 |
233 | ;;;###autoload
234 | (defun flymake-grammarly-load ()
235 | "Configure flymake mode to check the current buffer's grammar."
236 | (interactive)
237 | (setq flymake-grammarly--last-buffer-string (buffer-string))
238 | (flymake-grammarly--grammar-check)
239 | (add-hook 'after-change-functions #'flymake-grammarly--after-change-functions nil t)
240 | (add-hook 'flymake-diagnostic-functions #'flymake-grammarly--checker nil t))
241 |
242 | ;;;###autoload
243 | (defun flymake-grammarly-unload ()
244 | "Unload grammarly checker for flymake."
245 | (interactive)
246 | (setq flymake-grammarly--last-buffer-string nil)
247 | (remove-hook 'after-change-functions #'flymake-grammarly--after-change-functions t)
248 | (remove-hook 'flymake-diagnostic-functions #'flymake-grammarly--checker t))
249 |
250 | ;;;###autoload
251 | (defun flymake-grammarly-maybe-load ()
252 | "Call `flymake-grammarly-load' if this file appears to be check for grammar."
253 | (interactive)
254 | (when (memq major-mode flymake-grammarly-active-modes)
255 | (flymake-grammarly-load)))
256 |
257 | (provide 'flymake-grammarly)
258 | ;;; flymake-grammarly.el ends here
259 |
--------------------------------------------------------------------------------