From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Ian Dunn Newsgroups: gmane.emacs.devel Subject: Re: [ELPA] New package: Auto Correct Mode Date: Sat, 02 Sep 2017 16:58:52 -0400 Message-ID: <87lglw6cmr.fsf@escafil> References: <87zialc7wx.fsf@escafil> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1504386043 22714 195.159.176.226 (2 Sep 2017 21:00:43 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Sat, 2 Sep 2017 21:00:43 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.0.50 (gnu/linux) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Sep 02 23:00:29 2017 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1doFWe-0004kI-RZ for ged-emacs-devel@m.gmane.org; Sat, 02 Sep 2017 23:00:21 +0200 Original-Received: from localhost ([::1]:56318 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1doFWj-0001lz-R8 for ged-emacs-devel@m.gmane.org; Sat, 02 Sep 2017 17:00:25 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:47349) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1doFVv-0001kh-LM for emacs-devel@gnu.org; Sat, 02 Sep 2017 16:59:40 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1doFVr-0002O6-1n for emacs-devel@gnu.org; Sat, 02 Sep 2017 16:59:35 -0400 Original-Received: from fencepost.gnu.org ([2001:4830:134:3::e]:57365) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1doFVq-0002O1-Pi for emacs-devel@gnu.org; Sat, 02 Sep 2017 16:59:30 -0400 Original-Received: from [2604:6000:1010:176:da4d:3352:bae5:f50e] (port=50158 helo=escafil) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1doFVp-0001SO-Qh for emacs-devel@gnu.org; Sat, 02 Sep 2017 16:59:30 -0400 In-Reply-To: (John Wiegley's message of "Sun, 27 Aug 2017 12:19:09 -0700") X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:217907 Archived-At: --=-=-= Content-Type: text/plain >>>>> "JW" == John Wiegley writes: JW> Maybe what you're offering is already possible, or can be JW> integrated directly with flyspell using a customization? Upon reviewing flyspell again, I realized that it is possible to integrate with it, which I think makes much more sense than having a package that stands entirely on its own. What I've done is modified auto-correct to make it more of an extension to ispell and flyspell, rather than its own interface. Corrections made through either of these packages will be added as automatic corrections. Further, I also added the ability to add support for handling corrections from any other package. I explain how to do this in the commentary. Summary of New Design: - Takes a correction from another package and stores it in an abbrev table - The abbrev table is only active in auto-correct-mode, to avoid cluttering the global abbrev table - The abbrev table is limited by auto-correct-predicate, which allows for better control - Integrates with Ispell and flyspell to add their corrections without having to use another correction interface - Can integrate with any other correction package --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=auto-correct.el Content-Transfer-Encoding: quoted-printable ;;; auto-correct.el --- Remembers and automatically fixes past corrections = -*- lexical-binding: t; -*- ;; Copyright (C) 2017 Free Software Foundation, Inc. ;; Author: Ian Dunn ;; Maintainer: Ian Dunn ;; Keywords: editing ;; Version: 1.0 ;; This file is part of GNU Emacs. ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; To enable, use: ;; M-x `auto-correct-mode' ;; After that, any future corrections made with flyspell or Ispell (or any = other ;; supported package) will be automatically corrected for you as you type. ;; For example, if you type "befroe" and fixed it with `ispell-word', ;; `auto-correct-mode' will change "befroe" to "before" every time you type= it ;; from then on. ;; Corrections are only made when `auto-correct-mode' is enabled. Expansio= n is ;; case-insensitive, so trying to fix alice as Alice won't work. ;; For more fine-grained control over when corrections are made, set the ;; buffer-local variable `auto-correct-predicate'. ;; Behind the scenes, auto-correct uses an abbrev table, so in order to cle= an ;; out or modify any fixes auto-correct has learned, use `list-abbrevs'. T= his ;; also means that fixes are saved between Emacs sessions along with the ab= brev ;; tables. ;; Ispell and flyspell are the only two packages that auto-correct supports= out ;; of the box, but it's possible to add support for any package that correc= ts ;; text: ;; 1. Create a function that calls `auto-correct--add-or-update-correction'= with ;; the old text and the corrected text from your package. ;; 2. Write two functions: an activation function and a deactivation functi= on. ;; These will be called by `auto-correct-mode' to activate and deactivate ;; support. ;; 3. Call `auto-correct--add-support' with your activation and deactivation ;; functions. ;; 4. You're done. ;;; Code: (eval-when-compile (require 'subr-x)) (require 'thingatpt) (defgroup auto-correct nil "Auto correction support." :prefix "auto-correct-" :group 'editing) ;; Core Functionality (defvar-local auto-correct-predicate nil "Predicate to check whether automatic corrections should be made. This should be a function of no arguments that returns non-nil if auto-correct should operate on the current text.") (defun auto-correct-expand-p () "Return non-nil if auto-correct should operate on the current point. To customize this behavior, set `auto-correct-predicate'." (when auto-correct-predicate (funcall auto-correct-predicate))) (define-abbrev-table 'auto-correct-abbrev-table nil "Abbrev table where automatic corrections are stored." :enable-function #'auto-correct-expand-p) (defun auto-correct--get-abbrev-table (local) "Get the abbrev table to use with auto-correct. If LOCAL is non-nil, use the local table if it exists. Otherwise, use auto-correct's abbrev table." (if local (or local-abbrev-table auto-correct-abbrev-table) auto-correct-abbrev-table)) (defun auto-correct--add-or-update-correction (before after &optional local) "Add or update a correction into auto-correct's table. BEFORE is the misspelled word, and AFTER is the correct spelling. Optional argument LOCAL determines whether to make the correction locally. If nil, the correction will be made whenever `auto-correct-mode' is enabled." (let ((table (auto-correct--get-abbrev-table local)) (bef (downcase before)) (aft (downcase after))) (define-abbrev table bef aft nil :count 1) ;; Save the abbrevs. (write-abbrev-file) (message "\"%s\" now expands to \"%s\"" bef aft))) ;; Extension Support (defvar auto-correct-activate-functions nil "Functions to run to activate auto-correct support in various packages. These are called by `auto-correct-mode' when it is enabled.") (defvar auto-correct-deactivate-functions nil "Functions to run to deactivate auto-correct support in various packages. These are called by `auto-correct-mode' when it is disabled.") (defvar auto-correct-mode) (defun auto-correct--add-support (activate-fun deactivate-fun) "Add auto-correct support for a spelling package. Support will be activated by ACTIVATE-FUN and deactivated by DEACTIVATE-FUN= ." (add-hook 'auto-correct-activate-functions activate-fun) (add-hook 'auto-correct-deactivate-functions deactivate-fun) ;; If `auto-correct-mode' is enabled, activate this package's support. (when auto-correct-mode (funcall activate-fun))) (defun auto-correct--remove-support (activate-fun deactivate-fun) "Remove support for a spelling package. Support will be activated by ACTIVATE-FUN and deactivated by DEACTIVATE-FUN= ." (remove-hook 'auto-correct-activate-functions activate-fun) (remove-hook 'auto-correct-deactivate-functions deactivate-fun) ;; If `auto-correct-mode' is enabled, deactivate this package's support. (when auto-correct-mode (funcall deactivate-fun))) (defun auto-correct--handle-support (add activate-fun deactivate-fun) "Helper function to add or remove auto-correct support for a package. If ADD is non-nil, add support, otherwise remove it. ACTIVATE-FUN and DEACTIVATE-FUN are exactly as they are in `auto-correct--add-support'." (if add (auto-correct--add-support activate-fun deactivate-fun) (auto-correct--remove-support activate-fun deactivate-fun))) ;; Flyspell Support (defvar flyspell-auto-correct-word) (defvar flyspell-use-global-abbrev-table-p) (defvar flyspell-insert-function) (defun auto-correct-flyspell-insert (word) "Insert WORD and add it as a correction. The original (misspelled) word is drawn from the variable `flyspell-auto-correct-word'. When `auto-correct-mode' is enabled, this function is set as `flyspell-insert-function'." (let ((old-word flyspell-auto-correct-word) (new-word word) (local (not flyspell-use-global-abbrev-table-p))) (auto-correct--add-or-update-correction old-word new-word local) (insert word))) (defun auto-correct--flyspell-activate () "Activate flyspell auto-correct support. Sets `flyspell-insert-function' to `auto-correct-flyspell-insert'." ;; Add flyspell corrections as auto-corrections (setq flyspell-insert-function 'auto-correct-flyspell-insert)) (defun auto-correct--flyspell-deactivate () "Deactivate flyspell auto-correct support." (setq flyspell-insert-function 'insert)) (defcustom auto-correct-enable-flyspell-support t "Whether to automatically correct corrections made in flyspell." :group 'auto-correct :type 'boolean :set (lambda (sym val) (set sym val) (auto-correct--handle-support val 'auto-correct--flyspell-activate 'auto-correct--flyspell-deactivate))) ;; Ispell support (defvar ispell-following-word) (defvar auto-correct--ispell-use-local-table nil "Whether to use the local table with Ispell. Toggle this interactively with `auto-correct-toggle-ispell-local'.") (defun auto-correct-toggle-ispell-local () "Toggle whether to use the local or auto-correct table for Ispell." (interactive) (setq auto-correct--ispell-use-local-table (not auto-correct--ispell-use-local-table)) (message "Auto-Correct is now using the %s table" (if auto-correct--ispell-use-local-table "local" "global"))) (defun auto-correct--ispell-handler (ispell-result) "Add ISPELL-RESULT as a correction. The original (misspelled) word is drawn from the function `word-at-point'. This is intended to be added as advice to `ispell-command-loop'." (when-let ((word-before (word-at-point)) (correction ispell-result)) (when (and correction (consp correction)) ;; The correction was entered by hand. (setq correction (car correction))) (if (and (not (or (eq correction 0) ;; Word was corrected from list (eq correction 'quit))) ;; Session was exited (not (equal word-before correction))) ;; Word was corrected (auto-correct--add-or-update-correction word-before correction auto-correct--ispell-use-local-ta= ble))) ispell-result) (defun auto-correct--ispell-activate () "Activate Ispell auto-correct support. Adds advice to `ispell-command-loop' that adds the result as a correction." ;; Add corrections from ispell as auto-corrections (advice-add 'ispell-command-loop :filter-return #'auto-correct--ispell-handler)) (defun auto-correct--ispell-deactivate () "Deactivate Ispell auto-correct support." (advice-remove 'ispell-command-loop #'auto-correct--ispell-handler)) (defcustom auto-correct-enable-ispell-support t "Whether to automatically correct corrections made in Ispell." :group 'auto-correct :type 'boolean :set (lambda (sym val) (set sym val) (auto-correct--handle-support val 'auto-correct--ispell-activate 'auto-correct--ispell-deactivate))) ;; Standalone (piggybacks on Ispell) ;;;###autoload (defun auto-correct-fix-and-add (local) "Use `ispell-word' to fix a misspelled word at point. Once the misspelled word is fixed, auto-correct will remember the fix and auto-correct it from then on, so long as `auto-correct-mode' is enabled. With a non-nil argument LOCAL (interactively, the prefix argument), create a fix for the typo that will be auto-corrected for buffers using the current local mode. This is pointless to use when `auto-correct-mode' is enabled; instead, use `ispell-word' and `auto-correct-toggle-ispell-local' to use the local abbrev table." (interactive "P") (let ((auto-correct--ispell-use-local-table local)) (auto-correct--ispell-handler (ispell-word ispell-following-word 'quiet= ly)))) ;;;###autoload (defun auto-correct-scan-buffer () "Scan current buffer for misspelled words. When a misspelled word is found, offer to correct the misspelled word and auto-correct the typo in the future. When `auto-correct-mode' is enabled, use the `ispell' command instead." (interactive) (save-excursion (goto-char (point-min)) ;; Stop from being prompted to save the personal dictionary after every ;; change. (cl-letf (((symbol-function 'ispell-pdict-save) #'ignore)) (while (forward-word) (auto-correct-fix-and-add nil))) (ispell-pdict-save))) ;;;###autoload (defun auto-correct-scan-region (start end) "Scan the region between START and END for misspelled words. Interactively, START and END are the current region. When a misspelled word is found, offer to correct the misspelled word and auto-correct the typo in the future. When `auto-correct-mode' is enabled, use the `ispell' command instead." (interactive "r") (save-restriction (narrow-to-region start end) (auto-correct-scan-buffer))) ;;;###autoload (defun auto-correct-scan () "Scan the buffer or region for misspelled words. When a misspelled word is found, offer to correct the misspelled word and auto-correct the typo in the future. When `auto-correct-mode' is enabled, use the `ispell' command instead." (interactive) (if (region-active-p) (auto-correct-scan-region (region-beginning) (region-end)) (auto-correct-scan-buffer))) ;; The mode ;;;###autoload (define-minor-mode auto-correct-mode "Activate automatic corrections. Auto correct expansions will only work when this mode is enabled, but auto-correct can be trained with `auto-correct-fix-and-add' even if this mode is disabled. When this mode is enabled, corrections made with flyspell and Ispell will be made automatically after fixing them once. In order to add corrections to the auto-correct abbrev table in flyspell (and thus have them corrected later), set `flyspell-use-global-abbrev-table-p' to non-nil. In order to set corrections as local using Ispell, use the command `auto-correct-toggle-ispell-local'. \\{auto-correct-mode-map}" :group 'auto-correct :global t :init-value nil :lighter " Auto-Correct" (if auto-correct-mode (run-hooks 'auto-correct-activate-functions) (run-hooks 'auto-correct-deactivate-functions))) ;; Only enable the abbrev list when auto-correct-mode is active. (add-to-list 'abbrev-minor-mode-table-alist `(auto-correct-mode ,auto-correct-abbrev-table) 'append #'equal) (provide 'auto-correct) ;;; auto-correct.el ends here --=-=-= Content-Type: text/plain -- Ian Dunn --=-=-=--