From: F. Jason Park Date: Sun, 5 Jan 2025 23:59:46 +0000 (-0800) Subject: Improve use of Flyspell's API in erc-spelling X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=2a3911dc57219dedd056e14bdb5ed36343f9b804;p=emacs.git Improve use of Flyspell's API in erc-spelling * etc/ERC-NEWS: Announce deprecation of `erc-spelling-flyspell-verify' and `erc-spelling-unhighlight-word'. A slight behavioral change not worth mentioning is that, previously, ERC arranged for Flyspell to ignore any word immediately following a forward slash anywhere in the prompt input, even those for which the slash served as mere punctuation (a "stroke"), as in "something/misspelt." As of this change, Flyspell only unconditionally exempts an initial slash-prepended word, like "tableflip" in "ERC> /tableflip", and checks all others that follow against known slash commands. * lisp/erc/erc-spelling.el: Change top-level assignment of `erc-mode' symbol-property `flyspell-mode-predicate' from `erc-spelling-flyspell-verify' to `erc-spelling--flyspell-input-p'. (erc-spelling-mode, erc-spelling-disable): Remove local member from `flyspell-incorrect-hook'. (erc-spelling-init): Add `erc-spelling--flyspell-check' to `flyspell-incorrect-hook' locally. Don't bother explicitly setting `flyspell-generic-check-word-predicate' because Flyspell already does that for clients using the `flyspell-mode-predicte' interface. (erc-spelling-flyspell-verify, erc-spelling-unhighlight-word): Mark obsolete. (erc-spelling--flyspell-check, erc-spelling--flyspell-input-p): New functions, essentially the two halves of a reworked and bifurcated `erc-spelling-flyspell-verify'. Though used as a predicate, the first is not named as such because it performs side effects. * test/lisp/erc/erc-scenarios-spelling.el: New file. * test/lisp/erc/resources/spelling/auto-correct.eld: New file. (Bug#75327) (cherry picked from commit f9ecde75bbcd315557481585a1146f976f719504) --- diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index f79d45b94b6..d491c3e5132 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -96,6 +96,11 @@ Although this has always been the case, string values are now more likely to be seen because ERC no longer coerces service names to port numbers. +*** The 'spelling' module makes better use of Flyspell's API. +As a consequence, the library functions 'erc-spelling-flyspell-verify' +and 'erc-spelling-unhighlight-word' are now unused and have been marked +obsolete. + * Changes in ERC 5.6 diff --git a/lisp/erc/erc-spelling.el b/lisp/erc/erc-spelling.el index 01e587af368..b354349bcec 100644 --- a/lisp/erc/erc-spelling.el +++ b/lisp/erc/erc-spelling.el @@ -48,6 +48,7 @@ (erc-spelling-init (current-buffer))))) ((remove-hook 'erc-connect-pre-hook #'erc-spelling-init) (dolist (buffer (erc-buffer-list)) + (remove-hook 'flyspell-incorrect-hook #'erc-spelling--flyspell-check t) (with-current-buffer buffer (flyspell-mode 0))))) (defcustom erc-spelling-dictionaries nil @@ -78,7 +79,7 @@ The current buffer is given by BUFFER." (if dicts (cadr (car dicts)) (erc-with-server-buffer ispell-local-dictionary))))) - (setq flyspell-generic-check-word-predicate #'erc-spelling-flyspell-verify) + (add-hook 'flyspell-incorrect-hook #'erc-spelling--flyspell-check 20 t) (flyspell-mode 1))) (defun erc-spelling-unhighlight-word (word) @@ -92,6 +93,7 @@ The cadr is the beginning and the caddr is the end." (defun erc-spelling-flyspell-verify () "Flyspell only the input line, nothing else." + (declare (obsolete erc-spelling--flyspell-input-p "31.1")) ;; FIXME: Don't use `flyspell-word'! (let ((word-data (and (boundp 'flyspell-word) flyspell-word))) @@ -109,9 +111,28 @@ The cadr is the beginning and the caddr is the end." nil) (t t))))) +;; Do this down here to avoid having to wrap the call sites above in +;; `with-suppressed-warnings'. +(make-obsolete 'erc-spelling-unhighlight-word + "value from `flyspell-get-word' now unused" "31.1") + +(defun erc-spelling--flyspell-check (beg end _) + "Return non-nil and remove overlay if text between BEG and END is correct." + (or (and erc-channel-users + (erc-get-channel-user (buffer-substring-no-properties beg end)) + (always (flyspell-unhighlight-at beg))) + (and erc-input-marker (> beg erc-input-marker) (eq (char-before beg) ?/) + (or (= beg (1+ erc-input-marker)) ; allow /misspelled at prompt + (erc-command-symbol (buffer-substring-no-properties beg end))) + (always (flyspell-unhighlight-at beg))))) + +(defun erc-spelling--flyspell-input-p () + "Return non-nil if Flyspell should check the prompt input at point." + (>= (point) erc-input-marker)) + (put 'erc-mode 'flyspell-mode-predicate - #'erc-spelling-flyspell-verify) + #'erc-spelling--flyspell-input-p) (provide 'erc-spelling) diff --git a/test/lisp/erc/erc-scenarios-spelling.el b/test/lisp/erc/erc-scenarios-spelling.el new file mode 100644 index 00000000000..a6660267fe8 --- /dev/null +++ b/test/lisp/erc/erc-scenarios-spelling.el @@ -0,0 +1,100 @@ +;;; erc-scenarios-spelling.el --- Basic spelling scenarios -*- lexical-binding: t -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +(require 'erc-spelling) + +(ert-deftest erc-scenarios-spelling--auto-correct () + :tags `(:expensive-test + :unstable + ,@(and (getenv "ERC_TESTS_GRAPHICAL") '(:erc--graphical))) + + ;; Allow running locally with SELECTOR=t if user has ispell configured. + (unless (ignore-errors + (and (executable-find ispell-program-name) + (progn (ispell-check-version) t) + (member "american" (ispell-valid-dictionary-list)))) + (ert-skip "Missing ispell program")) + + (ert-with-temp-directory erc-scenarios-spelling + + (erc-scenarios-common-with-noninteractive-in-term + ((erc-scenarios-common-dialog "spelling") + (process-environment (cons + (format "HOME=%s" erc-scenarios-spelling) + process-environment)) + (dumb-server (erc-d-run "localhost" t 'auto-correct)) + (port (process-contact dumb-server :service)) + (expect (erc-d-t-make-expecter)) + (erc-autojoin-channels-alist '((foonet "#chan"))) + (erc-modules (cons 'spelling erc-modules)) + (erc-server-flood-penalty 0.1)) + + (ert-info ("Connect to foonet") + (with-current-buffer (erc :server "127.0.0.1" + :port port + :nick "tester" + :full-name "tester") + (funcall expect 10 "no longer marked as being") + (should erc-spelling-mode) + (should flyspell-mode))) + + (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan")) + (should erc-spelling-mode) + (should flyspell-mode) + (funcall expect 10 " tester, welcome!") + + ;; Insert a command with one misspelled word. + (set-window-buffer nil (current-buffer)) + (execute-kbd-macro "\M->/AMSG an/dor /gmsg one fsbot two frob my shoe") + (funcall expect 10 "shoe") + + (let* ((ovs (overlays-in erc-input-marker (point))) + (ov1 (pop ovs)) + (ov2 (pop ovs))) + ;; At this point, flyspell should have done its thing. There + ;; should be two overlays: one on "dor" and the other on + ;; "frob". The spelling module's modifications should have + ;; prevented the two valid slash commands as well as "fsbot" + ;; from being highlighted. + (should-not ovs) + (should (flyspell-overlay-p ov1)) + (should (equal "dor" (buffer-substring (overlay-start ov1) + (overlay-end ov1)))) + (should (flyspell-overlay-p ov2)) + (should (equal "frob" (buffer-substring (overlay-start ov2) + (overlay-end ov2)))) + (goto-char (overlay-start ov2)) + + ;; Depending on the machine, this should become something + ;; like: "/AMSG an/dor /gmsg one fsbot two Rob my shoe". + (execute-kbd-macro (key-parse "M-TAB")) + (should (equal (overlays-in erc-input-marker (point-max)) + (list ov1))))) + + (when noninteractive + (erc-spelling-mode -1))))) + +;;; erc-scenarios-spelling.el ends here diff --git a/test/lisp/erc/resources/spelling/auto-correct.eld b/test/lisp/erc/resources/spelling/auto-correct.eld new file mode 100644 index 00000000000..0f00ee4825c --- /dev/null +++ b/test/lisp/erc/resources/spelling/auto-correct.eld @@ -0,0 +1,35 @@ +;; -*- mode: lisp-data; -*- +((nick 1 "NICK tester")) +((user 1 "USER user 0 * :tester") + (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester") + (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version oragono-2.6.0-7481bf0385b95b16") + (0 ":irc.foonet.org 003 tester :This server was created Wed, 05 May 2021 09:05:34 UTC") + (0 ":irc.foonet.org 004 tester irc.foonet.org oragono-2.6.0-7481bf0385b95b16 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv") + (0 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this server") + (0 ":irc.foonet.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server") + (0 ":irc.foonet.org 005 tester draft/CHATHISTORY=100 :are supported by this server") + (0 ":irc.foonet.org 251 tester :There are 0 users and 3 invisible on 1 server(s)") + (0 ":irc.foonet.org 252 tester 0 :IRC Operators online") + (0 ":irc.foonet.org 254 tester 1 :channels formed") + (0 ":irc.foonet.org 255 tester :I have 3 clients and 0 servers") + (0 ":irc.foonet.org 265 tester 3 3 :Current local users 3, max 3") + (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3") + (0 ":irc.foonet.org 422 tester :MOTD File is missing")) + +((mode-user 10 "MODE tester +i") + (0 ":irc.foonet.org 221 tester +i") + (0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.") + (0 ":irc.foonet.org 305 tester :You are no longer marked as being away")) + +((join 10 "JOIN #chan") + (0 ":tester!~u@247eaxkrufj44.irc JOIN #chan") + (0 ":irc.foonet.org 353 tester = #chan :alice fsbot @bob tester") + (0 ":irc.foonet.org 366 tester #chan :End of /NAMES list.")) + +((mode-chan 10 "MODE #chan") + (0 ":irc.foonet.org 324 tester #chan +nt") + (0 ":irc.foonet.org 329 tester #chan 1620205534") + (0 ":alice!~u@yppdd5tt4admc.irc PRIVMSG #chan :tester, welcome!") + (0 ":bob!~u@yppdd5tt4admc.irc PRIVMSG #chan :tester, welcome!") + (0 ":bob!~u@yppdd5tt4admc.irc PRIVMSG #chan :alice: Nor I no strength to climb without thy help.") + (0 ":alice!~u@yppdd5tt4admc.irc PRIVMSG #chan :bob: Nothing, but let him have thanks."))