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