├── README.org └── writegood-mode.el /README.org: -------------------------------------------------------------------------------- 1 | * Writegood Mode 2 | 3 | This is a minor mode to aid in finding common writing problems. [[http://matt.might.net/articles/shell-scripts-for-passive-voice-weasel-words-duplicates/][Matt 4 | Might's weaselwords scripts]] inspired this mode. 5 | 6 | It highlights text based on a set of weasel-words, passive-voice and 7 | duplicate words. 8 | 9 | * Basic Usage 10 | 11 | First, load in the mode. 12 | 13 | : (add-to-list 'load-path "path/to/writegood-mode") 14 | : (require 'writegood-mode) 15 | : (global-set-key "\C-cg" 'writegood-mode) 16 | 17 | I use the command key above to start the mode when I wish to check my 18 | writing. 19 | 20 | Alternatively, this package is also available on MELPA. If installed 21 | through the package manager, then only the global key customization 22 | would be necessary. 23 | 24 | * Readability tests 25 | 26 | There are now two functions to perform [[http://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests][Flesch-Kincaid scoring]] and 27 | grade-level estimation. These follow the known algorithms, but may 28 | differ from other implementations due to the syllable estimation. 29 | 30 | I use these global keys to get to the readability tests: 31 | 32 | : (global-set-key "\C-c\C-gg" 'writegood-grade-level) 33 | : (global-set-key "\C-c\C-ge" 'writegood-reading-ease) 34 | 35 | * Customization 36 | 37 | The user is free to customize three main portions of the mode. 38 | 39 | ** Faces 40 | 41 | The three faces used pull from the default warning face and add 42 | subtle backgrounds. There is a separate face for each check performed. 43 | 44 | - Weasel words (~writegood-weasels-face~) 45 | - Passive voice (~writegood-passive-voice-face~) 46 | - Duplicate words (~writegood-duplicates-face~) 47 | 48 | ** Weasel Words 49 | 50 | There is a large list of included weasel words, but you may have 51 | your own. See the ~write-good-weasel-words~ variable to modify this 52 | list. 53 | 54 | Additionally, if you require more than a list of words to mark up 55 | your content, then there is a custom variable, 56 | ~writegood-weasel-words-additional-regexp~ that allows a custom 57 | regular expression for matching whatever you heart desires 58 | (expressed as a regex). 59 | 60 | ** Passive Voice Irregulars 61 | 62 | There is also a list of irregular passive voice verbs. These are 63 | the verbs that do not end in 'ed' to signify past tense. This 64 | variable allow the user to modify the list as needed. 65 | 66 | Similar to weasel words, the custom variable 67 | ~writegood-passive-voice-irregulars-additional-regexp~ allows the use 68 | of an additional regular expression to highlight passive voice irregulars. 69 | 70 | 71 | * Alternatives 72 | 73 | [[https://github.com/sachac/artbollocks-mode][Artbollocks]] looks to be an alternative mode to this one. 74 | -------------------------------------------------------------------------------- /writegood-mode.el: -------------------------------------------------------------------------------- 1 | ;;; writegood-mode.el --- Polish up poor writing on the fly 2 | ;; 3 | ;; Author: Benjamin Beckwith 4 | ;; Created: 2010-8-12 5 | ;; Version: 2.2.0 6 | ;; Last-Updated: 2015-03-25 7 | ;; URL: http://github.com/bnbeckwith/writegood-mode 8 | ;; Keywords: writing weasel-words grammar 9 | ;; Compatability: 10 | ;; 11 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 12 | ;; 13 | ;;; Commentary: 14 | ;; 15 | ;; This minor mode tries to find and highlight problems with your 16 | ;; writing (in english). 17 | ;; 18 | ;; Behavior inspired by the weaselwords scripts to aid in good 19 | ;; writing. 20 | ;; http://matt.might.net/articles/shell-scripts-for-passive-voice-weasel-words-duplicates/ 21 | ;; 22 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 23 | ;; 24 | ;;; Change Log: 25 | ;; 26 | ;; 2.2.0 Add tooltips to explain detected text 27 | ;; 2.1.1 Fix bug with regex definition 28 | ;; 2.1.0 Add capability to add custom regexps 29 | ;; 2.0.4 Remove cl dependency 30 | ;; 2.0.3 Add in a small decription of the Flesch-Kincaid score 31 | ;; 2.0.2 Fix Formatting in Org-mode files, make faces underline 32 | ;; 2.0.1 Make user additions to word lists dynamic 33 | ;; 2.0.0 Flesch-Kincaid scoring added to functionality 34 | ;; 1.3.0 Several pull requests added, comments checked, passive voice regexp fixed 35 | ;; 1.2.0 Fixed weasel-words regexp to have word boundaries 36 | ;; 1.1.0 Fixed regexps to be multiline. 37 | ;; 1.0.0 Initial version 38 | ;; 39 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 40 | ;; 41 | ;; This program is free software; you can redistribute it and/or 42 | ;; modify it under the terms of the GNU General Public License as 43 | ;; published by the Free Software Foundation; either version 3, or 44 | ;; (at your option) any later version. 45 | ;; 46 | ;; This program is distributed in the hope that it will be useful, 47 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 48 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 49 | ;; General Public License for more details. 50 | ;; 51 | ;; You should have received a copy of the GNU General Public License 52 | ;; along with this program; see the file COPYING. If not, write to 53 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 54 | ;; Floor, Boston, MA 02110-1301, USA. 55 | ;; 56 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 57 | ;; 58 | ;;; Test Text: 59 | ;; 60 | ;; This mode will improve various aspects of your writing in many ways. 61 | ;; With this mode, text within comments will be searched for the 62 | ;; the duplicate problem. 63 | ;; The text is searched and aspects (even within comments) are 64 | ;; highlighted. 65 | ;; Another benefit is the the finding of duplicates. 66 | ;; 67 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 68 | ;; 69 | ;;; Code: 70 | 71 | (require 'regexp-opt) 72 | (require 'faces) 73 | 74 | (defgroup writegood nil 75 | "Minor mode for highlighting bad english writing." 76 | :prefix "writegood-" 77 | :group 'help 78 | :link '(url-link "http://github.com/bnbeckwith/writegood-mode")) 79 | 80 | (defconst writegood-version "2.2.0" 81 | "WriteGood mode version") 82 | 83 | ;; General Custom settings 84 | (defcustom writegood-sentence-punctuation 85 | '(?. ?? ?!) 86 | "List of punctuation denoting sentence end" 87 | :group 'writegood 88 | :type '(repeat character)) 89 | 90 | ;; Weaselwords 91 | (defface writegood-weasels-face 92 | '((((supports :underline (:style wave))) 93 | :underline (:style wave :color "DarkOrange")) 94 | (((class color) (background light)) 95 | (:inherit font-lock-warning-face :background "moccasin")) 96 | (((class color) (background dark)) 97 | (:inherit font-lock-warning-face :background "DarkOrange"))) 98 | "Writegood face for weasel words" 99 | :group 'writegood) 100 | 101 | (defcustom writegood-weasel-words 102 | '("many" "various" "very" "fairly" "several" "extremely" 103 | "exceedingly" "quite" "remarkably" "few" "surprisingly" 104 | "mostly" "largely" "huge" "tiny" "are a number" "is a number" 105 | "excellent" "interestingly" "significantly" "substantially" 106 | "clearly" "vast" "relatively" "completely" "literally" 107 | "not rocket science" "outside the box") 108 | "The weasel words to use" 109 | :group 'writegood 110 | :type '(repeat string)) 111 | 112 | (defcustom writegood-weasel-words-tooltip "Weasel word: consider removing or replacing" 113 | "Message to show for weasel words" 114 | :group 'writegood 115 | :type 'string) 116 | 117 | (defcustom writegood-weasel-words-additional-regexp 118 | nil 119 | "Additional regexp to identify weasel words." 120 | :group 'writegood 121 | :type 'regexp) 122 | 123 | (defun writegood-weasels-font-lock-keywords-regexp () 124 | "Generate regex that matches weasel-words" 125 | (concat "\\b\\(?:" (regexp-opt writegood-weasel-words) 126 | (when writegood-weasel-words-additional-regexp 127 | (concat "\\|" writegood-weasel-words-additional-regexp)) 128 | "\\)\\b")) 129 | 130 | (defun writegood-weasels-font-lock-keywords () 131 | `((,(writegood-weasels-font-lock-keywords-regexp) 132 | 0 '(face writegood-weasels-face help-echo ,writegood-weasel-words-tooltip) prepend))) 133 | 134 | ;; Passive Voice 135 | (defface writegood-passive-voice-face 136 | '((((supports :underline (:style wave))) 137 | :underline (:style wave :color "cyan")) 138 | (((class color)) 139 | (:inherit font-lock-warning-face :background "LemonChiffon"))) 140 | "Writegood face for passive-voice" 141 | :group 'writegood) 142 | 143 | (defcustom writegood-passive-voice-irregulars 144 | '("awoken" "been" "born" "beat" "become" "begun" "bent" "beset" 145 | "bet" "bid" "bidden" "bound" "bitten" "bled" "blown" "broken" 146 | "bred" "brought" "broadcast" "built" "burnt" "burst" "bought" 147 | "cast" "caught" "chosen" "clung" "come" "cost" "crept" "cut" 148 | "dealt" "dug" "dived" "done" "drawn" "dreamt" "driven" "drunk" 149 | "eaten" "fallen" "fed" "felt" "fought" "found" "fit" "fled" 150 | "flung" "flown" "forbidden" "forgotten" "foregone" "forgiven" 151 | "forsaken" "frozen" "gotten" "given" "gone" "ground" "grown" 152 | "hung" "heard" "hidden" "hit" "held" "hurt" "kept" "knelt" "knit" 153 | "known" "laid" "led" "leapt" "learnt" "left" "lent" "let" "lain" 154 | "lighted" "lost" "made" "meant" "met" "misspelt" "mistaken" "mown" 155 | "overcome" "overdone" "overtaken" "overthrown" "paid" "pled" "proven" 156 | "put" "quit" "read" "rid" "ridden" "rung" "risen" "run" "sawn" 157 | "said" "seen" "sought" "sold" "sent" "set" "sewn" "shaken" "shaven" 158 | "shorn" "shed" "shone" "shod" "shot" "shown" "shrunk" "shut" 159 | "sung" "sunk" "sat" "slept" "slain" "slid" "slung" "slit" 160 | "smitten" "sown" "spoken" "sped" "spent" "spilt" "spun" "spit" 161 | "split" "spread" "sprung" "stood" "stolen" "stuck" "stung" 162 | "stunk" "stridden" "struck" "strung" "striven" "sworn" "swept" 163 | "swollen" "swum" "swung" "taken" "taught" "torn" "told" "thought" 164 | "thrived" "thrown" "thrust" "trodden" "understood" "upheld" "upset" 165 | "woken" "worn" "woven" "wed" "wept" "wound" "won" "withheld" 166 | "withstood" "wrung" "written") 167 | "List of passive voice irregular verbs" 168 | :group 'writegood 169 | :type '(repeat string)) 170 | 171 | (defcustom writegood-passive-voice-irregulars-additional-regexp 172 | nil 173 | "Additional regexp for passive voice irregulars" 174 | :group 'writegood 175 | :type 'regexp) 176 | 177 | (defcustom writegood-passive-voice-tooltip "Switch to active voice" 178 | "Message to show for passive-voice text" 179 | :group 'writegood 180 | :type 'string) 181 | 182 | (defun writegood-passive-voice-font-lock-keywords-regexp () 183 | "Generate font-lock keywords regexp for passive-voice" 184 | (concat "\\b\\(am\\|are\\|were\\|being\\|is\\|been\\|was\\|be\\)\\b\\([[:space:]]\\|\\s<\\|\\s>\\)+\\([[:word:]]+ed\\|" 185 | (regexp-opt writegood-passive-voice-irregulars) 186 | (when writegood-passive-voice-irregulars-additional-regexp 187 | (concat "\\)\\|\\(" writegood-passive-voice-irregulars-additional-regexp)) 188 | "\\)\\b")) 189 | 190 | (defun writegood-passive-voice-font-lock-keywords () 191 | `((,(writegood-passive-voice-font-lock-keywords-regexp) 192 | 0 '(face writegood-passive-voice-face help-echo ,writegood-passive-voice-tooltip) prepend))) 193 | 194 | ;; Duplicates 195 | (defface writegood-duplicates-face 196 | '((((supports :underline (:style wave))) 197 | :underline (:style wave :color "DeepPink")) 198 | (((class color) (background light)) 199 | (:inherit font-lock-warning-face :background "MistyRose")) 200 | (((class color) (background dark)) 201 | (:inherit font-lock-warning-face :background "DeepPink"))) 202 | "Writegood face for duplicate words" 203 | :group 'writegood) 204 | 205 | (defcustom writegood-duplicates-tooltip "Duplicates detected" 206 | "Message to show for duplicated words" 207 | :group 'writegood 208 | :type 'string) 209 | 210 | (defvar writegood-duplicates-font-lock-keywords-regexp 211 | "\\b\\([[:word:]]+\\)\\([[:space:]]\\|\\s<\\|\\s>\\)+\\1\\b" 212 | "Font-lock keywords for duplicates") 213 | 214 | (defun writegood-duplicates-font-lock-keywords () 215 | `((,writegood-duplicates-font-lock-keywords-regexp 216 | 0 '(face writegood-duplicates-face help-echo ,writegood-duplicates-tooltip) prepend))) 217 | 218 | ;;;;;;;;;;;;;;;;;;;; Functions: 219 | 220 | (defun writegood-version () 221 | "Tell the version you are using" 222 | (interactive) 223 | (message writegood-version)) 224 | 225 | (defun writegood-weasels-turn-on () 226 | "Turn on syntax highlighting for weasels" 227 | (font-lock-add-keywords nil (writegood-weasels-font-lock-keywords) t)) 228 | 229 | (defun writegood-passive-voice-turn-on () 230 | "Turn on warnings for passive voice" 231 | (font-lock-add-keywords nil (writegood-passive-voice-font-lock-keywords) t)) 232 | 233 | (defun writegood-duplicates-turn-on () 234 | "Turn on warnings for duplicate words" 235 | (font-lock-add-keywords nil (writegood-duplicates-font-lock-keywords) t)) 236 | 237 | (defun writegood-weasels-turn-off () 238 | "Turn on syntax highlighting for weasels" 239 | (font-lock-remove-keywords nil (writegood-weasels-font-lock-keywords))) 240 | 241 | (defun writegood-passive-voice-turn-off () 242 | "Turn on warnings for passive voice" 243 | (font-lock-remove-keywords nil (writegood-passive-voice-font-lock-keywords))) 244 | 245 | (defun writegood-duplicates-turn-off () 246 | "Turn on warnings for duplicate words" 247 | (font-lock-remove-keywords nil (writegood-duplicates-font-lock-keywords))) 248 | 249 | (defun writegood-turn-on () 250 | "Turn on writegood-mode." 251 | (make-local-variable 'font-lock-keywords-case-fold-search) 252 | (setq font-lock-keywords-case-fold-search t) 253 | (writegood-weasels-turn-on) 254 | (writegood-passive-voice-turn-on) 255 | (writegood-duplicates-turn-on)) 256 | 257 | (defun writegood-turn-off () 258 | "Turn off writegood-mode." 259 | (writegood-weasels-turn-off) 260 | (writegood-passive-voice-turn-off) 261 | (writegood-duplicates-turn-off)) 262 | 263 | (defun writegood-count-words (rstart rend) 264 | "Count the words specified by the region bounded by RSTART and REND." 265 | (if (boundp 'count-words) 266 | (count-words rstart rend) 267 | (how-many "[[:word:]]+" rstart rend))) 268 | 269 | (defun writegood-count-sentences (rstart rend) 270 | "Count the sentences specified by the region bounded by RSTART and REND." 271 | (how-many (regexp-opt-charset writegood-sentence-punctuation) rstart rend)) 272 | 273 | (defun writegood-count-syllables (rstart rend) 274 | "Count the (approximate) number of syllables in the region bounded by RSTART and REND. 275 | 276 | Consecutive vowels count as one syllable. The endings -es -ed 277 | and -e are not counted as syllables. 278 | " 279 | (- (how-many "[aeiouy]+" rstart rend) 280 | (how-many "\\(es\\|ed\\|e\\)\\b" rstart rend))) 281 | 282 | (defun writegood-fk-parameters (&optional rstart rend) 283 | "Flesch-Kincaid reading parameters" 284 | (let* ((start (cond (rstart rstart) 285 | ((and transient-mark-mode mark-active) (region-beginning)) 286 | ('t (point-min)))) 287 | (end (cond (rend rend) 288 | ((and transient-mark-mode mark-active) (region-end)) 289 | ('t (point-max)))) 290 | (words (float (writegood-count-words start end))) 291 | (syllables (float (writegood-count-syllables start end))) 292 | (sentences (float (writegood-count-sentences start end)))) 293 | (list sentences words syllables))) 294 | 295 | (defun writegood-reading-ease-score->comment (score) 296 | "Rough interpreation of the Flesch-Kincaid Reading ease SCORE. 297 | 298 | From Wikipedia URL `https://en.wikipedia.org/wiki/Flesch–Kincaid_readability_tests'." 299 | (cond 300 | ((< score 0) "Ouch! (Proust literature)") 301 | ((and (<= 0 score) (< score 30.0)) "Very difficult (college graduate)") 302 | ((and (<= 30.0 score) (< score 50.0)) "Difficult (almost college)") 303 | ((and (<= 50.0 score) (< score 60.0)) "Fairly difficult (10-12th grade)") 304 | ((and (<= 60.0 score) (< score 70.0)) "Plain English (8-9th grade)") 305 | ((and (<= 70.0 score) (< score 80.0)) "Fairly easy (7th grade)") 306 | ((and (<= 80.0 score) (< score 90.0)) "Easy (6th grade)") 307 | ((<= 90.0 score) "Very easy (5th grade)"))) 308 | 309 | ;;;###autoload 310 | (defun writegood-reading-ease (&optional start end) 311 | "Flesch-Kincaid reading ease test in the region bounded by START and END. 312 | 313 | Scores roughly between 0 and 100." 314 | (interactive) 315 | (let* ((params (writegood-fk-parameters start end)) 316 | (sentences (nth 0 params)) 317 | (words (nth 1 params)) 318 | (syllables (nth 2 params)) 319 | (score (- 206.835 (* 1.015 (/ words sentences)) (* 84.6 (/ syllables words))))) 320 | (message "Flesch-Kincaid reading ease score: %.2f %s" score 321 | (writegood-reading-ease-score->comment score)))) 322 | 323 | ;;;###autoload 324 | (defun writegood-grade-level (&optional start end) 325 | "Flesch-Kincaid grade level test. Converts reading ease score to a grade level (Score ~ years of school needed to read passage)." 326 | (interactive) 327 | (let* ((params (writegood-fk-parameters start end)) 328 | (sentences (nth 0 params)) 329 | (words (nth 1 params)) 330 | (syllables (nth 2 params)) 331 | (score (+ (* 0.39 (/ words sentences)) (* 11.8 (/ syllables words)) -15.59))) 332 | (message "Flesch-Kincaid grade level score: %.2f" score))) 333 | 334 | ;;;###autoload 335 | (define-minor-mode writegood-mode 336 | "Colorize issues with the writing in the buffer." 337 | :lighter " Wg" 338 | (progn 339 | (if writegood-mode 340 | (writegood-turn-on) 341 | (writegood-turn-off)) 342 | (font-lock-mode 1))) 343 | 344 | (provide 'writegood-mode) 345 | 346 | ;;; writegood-mode.el ends here 347 | --------------------------------------------------------------------------------