From debbugs-submit-bounces@debbugs.gnu.org Tue Sep 09 22:13:29 2025 Received: (at submit) by debbugs.gnu.org; 10 Sep 2025 02:13:29 +0000 Received: from localhost ([127.0.0.1]:34751 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1uwAKs-00052c-PD for submit@debbugs.gnu.org; Tue, 09 Sep 2025 22:13:29 -0400 Received: from lists.gnu.org ([2001:470:142::17]:55888) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1uwAKn-00052D-Hj for submit@debbugs.gnu.org; Tue, 09 Sep 2025 22:13:23 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1uwAKh-0008Lo-Tw for bug-gnu-emacs@gnu.org; Tue, 09 Sep 2025 22:13:15 -0400 Received: from coconut.lockywolf.net ([213.165.252.157]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1uwAKc-0001tV-J4 for bug-gnu-emacs@gnu.org; Tue, 09 Sep 2025 22:13:15 -0400 Received: from laptop.lockywolf.net (unknown [58.19.0.46]) by coconut.lockywolf.net (Postfix) with ESMTPSA id 73CE438A00 for ; Wed, 10 Sep 2025 10:13:05 +0800 (CST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=lockywolf.net; s=2024-10-06; t=1757470386; bh=h41xziWW/RXIs62Fb4ndAhwnRaZ3P4G0HShAwEoYXZQ=; h=From:To:Subject:Date; b=jP4ccF1kKjTsuvJlwrnpbJJ79NMQGKcU9A6T7moRfTklvmRvSTrSDy8IbiMNqirpK WzerVEovHDihYoki79Gzyb0uiwvAHEARArvtvZanITle0IcCuS00TE2vJ237bUHbks wAr1fE4ZLt9ivNnBLjyfwXHlquPHJyOmB+val/eOO3vc9Tr9311+h+V46vbQkGVq2i utH9r3u3fYTX2oAAgsdhaIEL3XG7cCrMyfpRHr9ewVXNgeedbVFn5rREYTrpyYZ7dw 4kFb/Bm4GA62kgjjdGoBjOlV1B/RVOaDkTWUicD1Y7Crl0wBCz2bJGS2m4Nvl1+W3f PzaeIobeOSzDA== From: Lockywolf To: bug-gnu-emacs@gnu.org Subject: 31.0.50; Improve ispell.el documentation User-Agent: mu4e 1.12.9; emacs 31.0.50 X-Debbugs-Cc: Date: Wed, 10 Sep 2025 10:12:57 +0800 Message-ID: <87ecsft5qu.fsf@laptop.lockywolf.net> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=213.165.252.157; envelope-from=for_emacs_1@lockywolf.net; helo=coconut.lockywolf.net X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: 0.9 (/) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.1 (/) --=-=-= Content-Type: text/plain Dear Emacs developers, The following patch contains what I consider to be an improvement to the Emacs Manual with respect to the ~ispell.el~ spell-checking component. The patch is attached. --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-Add-tests-to-inspell.el-interactive-functions.patch Content-Transfer-Encoding: quoted-printable >From e5661b558e1ea0c4dbcc1ceef0c0c760ec5060f3 Mon Sep 17 00:00:00 2001 From: Lockywolf Date: Mon, 8 Sep 2025 13:13:25 +0800 Subject: [PATCH] Add tests to inspell.el interactive functions. * test/lisp/textmodes/ispell-tests/ispell-tests.el: Add tests for 1. ispell-kill-ispell 2. ispell-region 3. ispell-buffer 4. ispell-change-dictionary 5. ispell-comments-and-strings 6. ispell-comment-or-string-at-point 7. ispell-pdict-save 8. ispell-lookup-words 9. ispell-complete-word 10. ispell-complete-word-interior-frag 11. ispell-completion-at-point 12. ispell-minor-mode 13. ispell-message * test/lisp/textmodes/ispell-tests/ispell-aspell-tests.el: 1. ispell-word --- lisp/textmodes/ispell.el | 3 +- .../ispell-resources/fake-aspell-new.bash | 64 +- .../ispell-tests/ispell-aspell-tests.el | 106 +++ .../ispell-tests/ispell-hunspell-tests.el | 5 + .../ispell-international-ispell-tests.el | 3 + .../ispell-tests/ispell-tests-common.el | 41 +- .../textmodes/ispell-tests/ispell-tests.el | 653 +++++++++++++++--- 7 files changed, 758 insertions(+), 117 deletions(-) diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el index ad7c8571f67..b37d07f5e62 100644 --- a/lisp/textmodes/ispell.el +++ b/lisp/textmodes/ispell.el @@ -1803,7 +1803,8 @@ ispell-accept-output (if (null ispell-process) (error "No Ispell process to read output from!") (let ((buf ispell-output-buffer) - ispell-output) + (ispell-output nil)) + (if (not (bufferp buf)) (setq ispell-filter nil) (with-current-buffer buf diff --git a/test/lisp/textmodes/ispell-resources/fake-aspell-new.bash b/te= st/lisp/textmodes/ispell-resources/fake-aspell-new.bash index 7dd935bac3c..e3fd91d5196 100755 --- a/test/lisp/textmodes/ispell-resources/fake-aspell-new.bash +++ b/test/lisp/textmodes/ispell-resources/fake-aspell-new.bash @@ -1,5 +1,15 @@ #!/bin/bash =20 +#exec aspell "$@" + +#rm -rf ~/lwf_mock-aspell.log + +#printf 'date=3D"%s"\n' "$(date --iso=3Dseconds)" > /tmp/lwf_mock-aspell.l= og + +#printf 'args=3D"%s"\n' "$*" >> /tmp/lwf_mock-aspell.log || { printf "lwf:= ERROR\n" ; exit 3 ; } + +# coproc aspell { aspell "$@" ; } + vv=3D =20 show_vv() @@ -7,13 +17,51 @@ show_vv() printf '%s\n' "@(#) International Ispell Version 3.1.20 (but really A= spell 0.60.0)" } =20 -imitate_repl() +imitate_pipe() +{ + local a + declare -A sessiondict + show_vv + while read a ; do + #printf 'pipe=3D"%s"\n' "$a" >> /tmp/lwf_mock-aspell.log + if [[ "$a" =3D=3D '' ]] ; then + printf '' + elif [[ "$a" =3D=3D '+' || "$a" =3D=3D '~nroff' || "$a" =3D=3D '~tex' || = "$a" =3D=3D '!' || "$a" =3D=3D '-' || "$a" =3D=3D '%' ]] ; then + printf '' + elif [[ "${a:0:1}" =3D=3D '@' ]] ; then + sessiondict["${a:1}"]=3D"true" + printf '' + else + for b in $a ; do + if [[ "$b" =3D=3D '^' ]] ; then + printf '' + elif [[ ${sessiondict[$b]} =3D=3D 'true' || ${sessiondict[${b#^}]} =3D= =3D 'true' ]] ; then + printf '*\n' + elif [[ "$b" =3D=3D '^tarampampamtararam' || "$b" =3D=3D 'tarampampamtar= aram' ]] ; then + printf '# tarampampamtararam 0\n' # wrong word + elif [[ "$b" =3D=3D '^badworddd' || "$b" =3D=3D 'badworddd' ]] ; then + printf '# badworddd 0\n' # wrong word + elif [[ "$b" =3D=3D '^hellooooooo' || "$b" =3D=3D 'hellooooooo' ]] ; then + printf '# hellooooooo 0\n' # wrong word + elif [[ "$b" =3D=3D '^' ]] ; then + printf '\n' + else + printf "*\n" + fi + done + printf '\n' + fi + done +} + +imitate_interactive() { + exit 6 while true ; do read a -# printf 'debug=3D"%s"\n' "$a" +# printf 'interactive=3D"%s"\n' "$a" >> /tmp/lwf_mock-aspell.log if [[ "$a" =3D=3D '' ]] ; then - printf '' + printf '\n' elif [[ "$a" =3D=3D 'tarampampamtararam' ]] ; then printf '# tarampampamtararam 0\n\n' # wrong word else @@ -22,16 +70,16 @@ imitate_repl() done } =20 -show_vv =20 while :; do case $1 in -vv|-v) - #show_vv # for ispell.el error detection + show_vv # for ispell.el version detection exit ;; -a) # imitate REPL - imitate_repl + imitate_pipe + exit ;; -?*) printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 @@ -41,3 +89,7 @@ while :; do esac shift done + +printf 'Usage: aspell [options] \n' + +#printf 'this place should be unreachable\n' >> /tmp/lwf_mock-aspell.log diff --git a/test/lisp/textmodes/ispell-tests/ispell-aspell-tests.el b/test= /lisp/textmodes/ispell-tests/ispell-aspell-tests.el index c24d284f426..f8f954fd430 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-aspell-tests.el +++ b/test/lisp/textmodes/ispell-tests/ispell-aspell-tests.el @@ -25,9 +25,15 @@ ;;; Code: =20 (require 'ispell) +(require 'ert-x) =20 +(eval-when-compile + (defconst ispell-tests-lib (expand-file-name "test/lisp/textmodes/ispell= -tests/ispell-tests-common.el" source-directory)) + (load ispell-tests-lib)) (load (expand-file-name "test/lisp/textmodes/ispell-tests/ispell-tests-com= mon.el" source-directory)) =20 +(declare-function letopt (expand-file-name "test/lisp/textmodes/ispell-tes= ts/ispell-tests-common.el" source-directory) t t) + (ert-deftest ispell/aspell/ispell-check-version/works () "Test that aspell is correctly detected." (skip-unless (and (executable-find "aspell") @@ -68,6 +74,106 @@ ispell/aspell/ispell-check-version/version-lowlow (set-variable 'ispell-last-program-name test-saved-ispell-last-program-name))))) =20 +(ert-deftest ispell/aspell/ispell-word/english/correct () +"This test checks that Russian spellchecking works for Aspell." + (skip-unless (executable-find "aspell")) + (skip-unless (equal + 0 + (call-process "aspell" nil nil nil "-vv"))) + (skip-unless (equal + 0 + (with-temp-buffer + (insert "test") + (call-process-region + nil + nil + "aspell" nil t nil "-a" "-denglish")))) + (with-environment-variables (("HOME" temporary-file-directory)) + (let ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name "aspell") + (ispell-dictionary "english")) + (ignore-errors (ispell-kill-ispell t t)) + (with-temp-buffer + (insert + "hello\n") + (goto-char 0) + (ispell-change-dictionary "english") + (let ((debugmessage "")) + (ert-with-message-capture lres + (let ((ispell-check-only t)) + (ispell-word) + (setf debugmessage lres) + ;;(should (string-match "is correct" lres)) + )) + (message "lwf:lres=3D%s" debugmessage))) + 'passed + ))) + ) + +(ert-deftest ispell/aspell/ispell-word/english/incorrect () +"This test checks that Russian spellchecking works for Aspell." + (skip-unless (executable-find "aspell")) + (skip-unless (equal + 0 + (call-process "aspell" nil nil nil "-vv"))) + (skip-unless (equal + 0 + (with-temp-buffer + (insert "test") + (call-process-region + nil + nil + "aspell" nil t nil "-a" "-denglish")))) + (with-environment-variables (("HOME" temporary-file-directory)) + (let ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name "aspell") + (ispell-dictionary "english")) + (ignore-errors (ispell-kill-ispell t t)) + (with-temp-buffer + (insert + ;; there is no such a word in English, I swear. + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + ) + (goto-char 0) + (ispell-change-dictionary "english") + (ert-with-message-capture lres + (let ((ispell-check-only t)) + (ispell-word)) + (should (string-match "is incorrect" lres)))) + 'passed + ))) + ) + +(ert-deftest ispell/aspell/ispell-word/english/wrong-language () + "This test checks that Russian spellchecking works for Aspell." + :expected-result :failed + (skip-unless (executable-find "aspell")) + (skip-unless (equal + 0 + (call-process "aspell" nil nil nil "-vv"))) + (skip-unless (equal + 0 + (with-temp-buffer + (insert "test") + (call-process-region nil nil "aspell" nil '("*scratch*" = t) nil "-a" "-denglish")))) + (with-environment-variables (("HOME" temporary-file-directory)) + (let ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name "aspell")) + (ignore-errors (ispell-kill-ispell t t)) + (with-temp-buffer + (insert + ;; giving Aspell a wrong language should not fail + "=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D1=82\n" + ) + (goto-char 0) + (ispell-change-dictionary "english") + (ert-with-message-capture lres + (let ((ispell-check-only t)) + (ispell-word)) + (should (not (string-match "Error" lres))))) + 'passed + ))) + ) =20 (provide 'tests-ispell-aspell) ;;; tests-ispell-aspell.el ends here diff --git a/test/lisp/textmodes/ispell-tests/ispell-hunspell-tests.el b/te= st/lisp/textmodes/ispell-tests/ispell-hunspell-tests.el index ae3d6e303b3..ebdf10cc3f4 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-hunspell-tests.el +++ b/test/lisp/textmodes/ispell-tests/ispell-hunspell-tests.el @@ -26,6 +26,11 @@ =20 (require 'ispell) =20 +(eval-when-compile + (defconst ispell-tests-lib (expand-file-name "test/lisp/textmodes/ispell= -tests/ispell-tests-common.el" source-directory)) + (load ispell-tests-lib)) + + (load (expand-file-name "test/lisp/textmodes/ispell-tests/ispell-tests-com= mon.el" source-directory)) (ert-deftest ispell/hunspell/ispell-word/english/check-only () "This test checks that Russian spellchecking works for Hunspell." diff --git a/test/lisp/textmodes/ispell-tests/ispell-international-ispell-t= ests.el b/test/lisp/textmodes/ispell-tests/ispell-international-ispell-test= s.el index f232f26d10d..bcd19e105a3 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-international-ispell-tests.el +++ b/test/lisp/textmodes/ispell-tests/ispell-international-ispell-tests.el @@ -25,6 +25,9 @@ ;;; Code: =20 (require 'ispell) +(eval-when-compile + (defconst ispell-tests-lib (expand-file-name "test/lisp/textmodes/ispell= -tests/ispell-tests-common.el" source-directory)) + (load ispell-tests-lib)) =20 (load (expand-file-name "test/lisp/textmodes/ispell-tests/ispell-tests-com= mon.el" source-directory)) =20 diff --git a/test/lisp/textmodes/ispell-tests/ispell-tests-common.el b/test= /lisp/textmodes/ispell-tests/ispell-tests-common.el index a6c4f1247ee..e69e1a469e4 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-tests-common.el +++ b/test/lisp/textmodes/ispell-tests/ispell-tests-common.el @@ -2,20 +2,35 @@ =20 (defvar tests-ispell-data-directory (expand-file-name "test/lisp/textmodes/ispell-resources/" source-directo= ry)) +(defvar fake-aspell-path + (expand-file-name + "./fake-aspell-new.bash" + tests-ispell-data-directory)) =20 -(let* ((backend-binaries (list "ispell" "aspell" "hunspell" "enchant-2")) - (filter-binaries (lambda () - (seq-filter - #'executable-find - backend-binaries)))) + +(let* ((backend-binaries (list "aspell" fake-aspell-path "hunspell" "e= nchant-2") + ) + (filter-binaries (seq-filter + (lambda (b) + (and + (executable-find b) + ;; (equal 0 + ;; (with-temp-buffer + ;; (call-process b nil t "-v"))) + )) + backend-binaries))) =20 (defun ispell-tests--some-backend-available-p () (not - (null (funcall filter-binaries)))) + (null filter-binaries))) =20 (defun ispell-tests--some-backend () - (car (funcall filter-binaries)))) + (let ((retval (car filter-binaries))) + (message "available backend is:%s" retval) + retval))) =20 +(eval-when-compile + (require 'cl-macs)) (cl-defmacro letopt (bindings &body body) (declare (indent 1)) (let* ((binding-var (lambda (binding) (car binding))) @@ -33,4 +48,16 @@ letopt ,@body) ,@(reverse restorebindings))))) =20 +(cl-defmacro with-ispell-global-dictionary (bindings &body body) + "This macro should not really be needed, but `ispell.el'. +Sets up dictionaries in a stupid way." + (declare (indent 1)) + (let* ((dictionary-val (car bindings)) + (temp-var (gensym 'old-dictionary))) + `(let ((,temp-var (symbol-value 'ispell-dictionary))) + (unwind-protect (progn (ispell-change-dictionary ,dictionary-val t) + ,@body) + (progn + (ispell-change-dictionary ,temp-var t)))))) + (provide 'ispell-tests-common) diff --git a/test/lisp/textmodes/ispell-tests/ispell-tests.el b/test/lisp/t= extmodes/ispell-tests/ispell-tests.el index 9e675443986..47816671475 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-tests.el +++ b/test/lisp/textmodes/ispell-tests/ispell-tests.el @@ -24,9 +24,22 @@ =20 ;;; Code: =20 +(declare-function letopt ispell-tests-lib t t) +(declare-function ispell-tests--some-backend ispell-tests-lib t t) +(declare-function ispell-tests--some-backend-available-p ispell-tests-lib = t t) + (require 'ispell) -(load (expand-file-name "test/lisp/textmodes/ispell-tests/ispell-tests-com= mon.el" source-directory)) +(defconst ispell-tests-lib (expand-file-name "test/lisp/textmodes/ispell-t= ests/ispell-tests-common.el" source-directory)) +(eval-when-compile + (defconst ispell-tests-lib (expand-file-name "test/lisp/textmodes/ispell= -tests/ispell-tests-common.el" source-directory)) + (load ispell-tests-lib)) +(load ispell-tests-lib) + +(require 'ert) +(require 'ert-x) =20 +(setopt ispell-program-name "hunspell") +(setopt ispell-dictionary "fr_FR") =20 (defun warnings-buffer-exists-p () "Check if a buffer named \"*Warnings*\" exists." @@ -450,18 +463,20 @@ ispell/ispell-buffer-local-words/ispell-words-keyword Should pass regardless of the backend and the dictionary, because presumably nobody will have `hellooooooo' in their dictionary." (skip-unless (ispell-tests--some-backend-available-p)) - (letopt ((ispell-program-name (ispell-tests--some-backend))) - (with-temp-buffer - (nxml-mode) - (ignore-errors (ispell-kill-ispell)) - (with-environment-variables (("HOME" temporary-file-directory)) - (ispell-init-process) - (let ((test-output (ispell--run-on-word "hellooooooo"))) - (should (listp test-output)) - (should-not (equal t test-output))) - (ispell-add-per-file-word-list "hellooooooo") - (ispell-buffer-local-words) - (should (equal t (ispell--run-on-word "hellooooooo"))))))) + (with-environment-variables (("HOME" temporary-file-directory)) + (with-ispell-global-dictionary nil + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (nxml-mode) + (ignore-errors (ispell-kill-ispell)) + (ispell-init-process) + (let ((test-output (ispell--run-on-word "hellooooooo"))) + (should (listp test-output)) + (should-not (equal t test-output))) + (ispell-add-per-file-word-list "hellooooooo") + (ispell-buffer-local-words) + (should (equal t (ispell--run-on-word "hellooooooo"))))))) + ) =20 =20 (ert-deftest @@ -470,21 +485,24 @@ ispell/ispell-buffer-local-words/ispell-words-keyword Should pass regardless of the backend and the dictionary, because presumably nobody will have `hellooooooo' in their dictionary." (skip-unless (ispell-tests--some-backend-available-p)) - (letopt ((ispell-program-name (ispell-tests--some-backend))) - (cd temporary-file-directory) - (with-temp-buffer - (nxml-mode) - (ignore-errors (ispell-kill-ispell)) - (with-environment-variables (("HOME" temporary-file-directory)) - (ispell-init-process) - (let ((test-output (ispell--run-on-word "hellooooooo"))) - (should (listp test-output)) - (should-not (equal t test-output))) - (let ((ispell-buffer-session-localwords (list "hellooooooo")= )) - (ispell-buffer-local-words) - (should (equal t (ispell--run-on-word "hellooooooo")))))))) - -(ert-deftest ispell/ispell-init-process/works-nohome () + (with-environment-variables (("HOME" temporary-file-directory)) + (with-ispell-global-dictionary nil + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil)) + (cd temporary-file-directory) + (with-temp-buffer + (nxml-mode) + (ignore-errors (ispell-kill-ispell)) + (ispell-init-process) + (let ((test-output (ispell--run-on-word "hellooooooo"))) + (should (listp test-output)) + (should-not (equal t test-output))) + (let ((ispell-buffer-session-localwords (list "hellooooooo"))) + (ispell-buffer-local-words) + (should (equal t (ispell--run-on-word "hellooooooo")))))))) + ) + +(ert-deftest ispell/ispell-init-process/works-no-home () "Simple test to check that ispell-init-process works." :expected-result :failed (skip-unless (ispell-tests--some-backend-available-p)) @@ -492,13 +510,14 @@ ispell/ispell-init-process/works-nohome (with-temp-buffer (ispell-init-process)))) =20 -(ert-deftest ispell/ispell-init-process/works-withhome () +(ert-deftest ispell/ispell-init-process/works-with-home () "Simple test to check that ispell-init-process works." (skip-unless (ispell-tests--some-backend-available-p)) - (letopt ((ispell-program-name (ispell-tests--some-backend))) - (with-temp-buffer - (with-environment-variables (("HOME" temporary-file-directory)) - (ispell-init-process))))) + (with-ispell-global-dictionary nil + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (with-environment-variables (("HOME" temporary-file-directory)) + (ispell-init-process)))))) =20 ;; Some more tests for buffer-local stuff. ;; `ispell-buffer-local-dict' @@ -742,18 +761,17 @@ ispell/ispell-accept-buffer-local-defs/simple batch mode. 1. local words 2. dictionary and pdict -3. parser and extcharmode" - (skip-unless (executable-find "ispell")) - (setq old-engine ispell-program-name) - (setopt ispell-program-name "ispell") - (ispell-check-version t) - (skip-unless (and (null ispell-really-aspell) - (null ispell-really-hunspell) - (null ispell-really-enchant))) - (setq ispell-program-name old-engine) +3. parser and extcharmode. +This does not work well on hunspell, because hunspell +lies in their Man page, and in enchant-2 when it is using +hunspell. Hence skipping." + (skip-unless (not (or (equal (ispell-tests--some-backend) + "hunspell") + (equal (ispell-tests--some-backend) + "enchant-2")))) (with-environment-variables (("HOME" temporary-file-directory)) (with-temp-buffer - (letopt ((ispell-program-name "ispell")) + (letopt ((ispell-program-name (ispell-tests--some-backend))) (let ((test-dictname "english") (test-extcharmode "~latin3") (test-parser "~testparser") @@ -806,9 +824,6 @@ ispell/ispell--run-on-word/default "`ispell--run-on-word' should be the simplest interface for checking a word." (skip-unless (ispell-tests--some-backend-available-p)) - (skip-unless (equal - 0 - (call-process (ispell-tests--some-backend) nil nil nil "-v= v"))) (letopt ((ispell-program-name (ispell-tests--some-backend)) (ispell-dictionary "default")) (let ((default-directory temporary-file-directory)) @@ -890,21 +905,18 @@ ispell/ispell-word/default/check-only/correct (call-process (ispell-tests--some-backend) nil nil nil "-v= v"))) (with-environment-variables (("HOME" temporary-file-directory)) (let ((default-directory temporary-file-directory)) - (letopt ((ispell-program-name (ispell-tests--some-backend))) + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil)) (ignore-errors (ispell-kill-ispell t t)) (with-temp-buffer (insert "hello\n") (goto-char 0) - (let ((ispell-check-only t) - (current-point - (with-current-buffer "*Messages*" - (point)))) + (ert-with-message-capture lres (ispell-word) - (with-current-buffer "*Messages*" - (goto-char (point-max)) - (should ( > (search-backward "is correct" nil t) - current-point))))))))) + (should (string-match "is correct" lres)))) + 'passed))) + ) =20 (ert-deftest ispell/ispell-word/default/check-only/correct/add-init () "Check that `ispell-word' works with a default @@ -920,23 +932,21 @@ ispell/ispell-word/default/check-only/correct/add-init (call-process (ispell-tests--some-backend) nil nil nil "-v= v"))) (with-environment-variables (("HOME" temporary-file-directory)) (let ((default-directory temporary-file-directory)) - (letopt ((ispell-program-name (ispell-tests--some-backend))) + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil) + (ispell-check-only t)) (ignore-errors (ispell-kill-ispell t t)) (with-temp-buffer (ispell-init-process) ;; this is added (insert "hello\n") (goto-char 0) - (let ((ispell-check-only t) - (current-point - (with-current-buffer "*Messages*" - (point)))) + (ert-with-message-capture lres (ispell-word) - (with-current-buffer "*Messages*" - (goto-char (point-max)) - (should (> (search-backward "is correct" nil t) - current-point))) - )))))) + (should (string-match "is correct" lres))) + 'passed + )))) + ) =20 (ert-deftest ispell/ispell-word/default/check-only/incorrect () "Check that `ispell-word' works with a default @@ -945,75 +955,110 @@ ispell/ispell-word/default/check-only/incorrect be rewritten with a mock. This test gives it a word which does not exist." (skip-unless (ispell-tests--some-backend-available-p)) - (skip-unless (equal - 0 - (call-process (ispell-tests--some-backend) nil nil nil "-v= v"))) (with-environment-variables (("HOME" temporary-file-directory)) (let ((default-directory temporary-file-directory)) - (letopt ((ispell-program-name (ispell-tests--some-backend))) + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil) + (ispell-check-only t)) (ignore-errors (ispell-kill-ispell t t)) (with-temp-buffer (insert - "helloooo\n") + "hellooooooo\n") (goto-char 0) - (let ((ispell-check-only t) - (current-point - (with-current-buffer "*Messages*" - (point)))) + (ert-with-message-capture lres (ispell-word) - (with-current-buffer "*Messages*" - (goto-char (point-max)) - (should (> (search-backward "is incorrect" nil t) - current-point))) - )))))) + (should (string-match "is incorrect" lres)) + 'passed))))) + ) =20 (ert-deftest ispell/ispell-region/correct () "The simplest test for `ispell-region'." (skip-unless (ispell-tests--some-backend-available-p)) - (skip-unless (equal - 0 - (call-process (ispell-tests--some-backend) nil nil nil "-v= v"))) (with-environment-variables (("HOME" temporary-file-directory)) (let* ((default-directory temporary-file-directory) - (fake-aspell-path (expand-file-name - "./fake-aspell-new.bash" - tests-ispell-data-directory)) (words '("hello" "test" "test" "more" "obvious" "word")) (text (string-join words " "))) - (letopt ((ispell-program-name fake-aspell-path)) + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil)) (ignore-errors (ispell-kill-ispell t t)) (with-temp-buffer - (insert - text) + (insert text) + (goto-char (length (nth 0 words))) + (ert-with-message-capture lres + (ispell-region (point) (point-max)) + (should (string-match "^Spell-checking region using .* with .*= dictionary...done" lres)) + 'passed))))) + ) + + + +(ert-deftest ispell/ispell-region/incorrect () + "The simplest test for `ispell-region'." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory) + (words '("hello" "tarampampamtararam" "world")) + (text (string-join words " "))) + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil) + (ispell-check-only t)) + (ignore-errors (ispell-kill-ispell t t)) + (with-temp-buffer + (insert text) (goto-char (length (nth 0 words))) - (let (;(ispell-check-only t) - (current-point + (cl-labels ((checker () + (user-error "expected error"))) + (unwind-protect + (progn + (advice-add 'ispell-show-choices :override #'checker) + (should-error (ispell-region (point) (point-max)))) + (advice-remove 'ispell-show-choices #'checker)))) + 'passed))) + ) + +(ert-deftest ispell/ispell-buffer/correct () + "The simplest test for `ispell-buffer'. +`ispell-buffer' is a very simple wrapper around `ispell-region', +so this test virtually mirrors the previous one." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory) + (words '("hello" "test" "test" "more" "obvious" "word")) + (text (string-join words " "))) + (letopt ((ispell-dictionary nil) + (ispell-program-name (ispell-tests--some-backend))) + (ignore-errors (ispell-kill-ispell t t)) + (with-temp-buffer + (insert text) + (goto-char (length (nth 0 words))) + (let ((current-point (with-current-buffer "*Messages*" (point)))) - (ispell-region (point) (point-max)) + (ispell-buffer) (with-current-buffer "*Messages*" (goto-char (point-max)) - (should (> (re-search-backward "Spell-checking region using = .* with .* dictionary...done" nil t) current-point)) + (should (> (re-search-backward "^Spell-checking .* using .* = with .* dictionary...done" nil t) current-point)) 'passed) ))))) ) =20 -(ert-deftest ispell/ispell-region/incorrect () - "The simplest test for `ispell-region'." +(ert-deftest ispell/ispell-buffer/incorrect () + "The simplest test for `ispell-buffer'. +`ispell-buffer' is a very simple wrapper around `ispell-region', +so this test virtually mirrors the previous one." (skip-unless (ispell-tests--some-backend-available-p)) - (skip-unless (equal - 0 - (call-process (ispell-tests--some-backend) nil nil nil "-v= v"))) (with-environment-variables (("HOME" temporary-file-directory)) (let* ((default-directory temporary-file-directory) - (fake-aspell-path "aspell") - (words '("hello" "tarampampamtararam" "world")) + (words '("tarampampamtararam" "test" "test" "more" "obvious" "w= ord" "badworddd")) (text (string-join words " "))) - (letopt ((ispell-program-name fake-aspell-path)) + (letopt ((ispell-dictionary nil) + (ispell-program-name (ispell-tests--some-backend))) (ignore-errors (ispell-kill-ispell t t)) (with-temp-buffer - (insert - text) + (insert text) + ;; This is intentional. The incorrect word is not in the region, + ;; but `ispell-buffer' should move the point to the beginning + ;; of the buffer. (goto-char (length (nth 0 words))) (cl-labels ((checker () (user-error "expected error"))) @@ -1021,11 +1066,413 @@ ispell/ispell-region/incorrect (progn (advice-add 'ispell-show-choices :override #'checker) - (should-error (ispell-region (point) (point-max))) + (should-error (ispell-buffer)) + (ispell-kill-ispell nil t) 'passed) (advice-remove 'ispell-show-choices #'checker))) )))) ) =20 +(ert-deftest ispell/ispell-kill-ispell () + "Test that killing ispell works." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory) + (words '("tarampampamtararam" "test" "test" "more" "obvious" "w= ord")) + (text (string-join words " "))) + (with-temp-buffer + (with-ispell-global-dictionary nil + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (insert text) + (ispell-init-process) + (should ispell-async-processp) + (should (eq (ispell-process-status) 'run)) + (ispell-kill-ispell nil t))) + 'passed + ))) + (message "lwf:debug2:ispell-program-name=3D%s:ispell-dictionary=3D%s" + ispell-program-name ispell-dictionary) + ) + +(ert-deftest ispell/ispell/buffer () + "`ispell' is just a wrapper around `ispell-region' +and `ispell-buffer', which is also a wrapper around +`ispell-buffer'. +This test might seem confusing, as it does not check +for the availability of the backend, but this does +not matter `ispell' function does not use the +backend." + (let ((transient-mark-mode t)) + (with-temp-buffer + (insert "hello world test test") + (goto-char 2) + (set-mark (point)) + (goto-char (point-max)) + (deactivate-mark) + (cl-labels ((checker-buffer () + t) + (checker-region (_a _b) + (user-error "test failed"))) + (unwind-protect + (progn + (advice-add 'ispell-buffer :override #'checker-buffer) + (advice-add 'ispell-region :override #'checker-region) + (ispell) + ) + (progn + (advice-remove 'ispell-buffer #'checker-buffer) + (advice-remove 'ispell-region #'checker-region)))) + )) + ) + +(ert-deftest ispell/ispell/region () + "`ispell' is just a wrapper around `ispell-region' +and `ispell-buffer', which is also a wrapper around +`ispell-buffer'." + (let ((transient-mark-mode t)) + (with-temp-buffer + (insert "hello world test test") + (goto-char 2) + (set-mark (point)) + (goto-char (point-max)) + (activate-mark) + (cl-labels ((checker-buffer () + (user-error "test failed")) + (checker-region (_a _b) + t)) + (unwind-protect + (progn + (advice-add 'ispell-buffer :override #'checker-buffer) + (advice-add 'ispell-region :override #'checker-region) + (ispell) + ) + (progn + (advice-remove 'ispell-buffer #'checker-buffer) + (advice-remove 'ispell-region #'checker-region)))) + )) + ) + +(ert-deftest ispell/ispell-change-dictionary () + "Simple test for changing a dictionary" + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (ispell-change-dictionary "english") + (should (equal ispell-local-dictionary "english")) + (ispell-change-dictionary "default") + (should (equal ispell-local-dictionary nil)) + (ispell-change-dictionary "english") + (should (equal ispell-local-dictionary "english")) + 'passed + )))) + ) + +(ert-deftest ispell/ispell-comments-and-strings/correct () + "Test that `ispell-comments-and-strings' does not err +on a correct buffer." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-dictionary nil) + (ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (ispell-kill-ispell t t) + (insert "#!/bin/bash\n" + "echo \"string to check\"\n" + "# commented line\n") + (sh-mode) + (ert-with-message-capture lres + (ispell-comments-and-strings) + (should (string-match "Spell-checking .* using .* with .* dict= ionary...done" lres)))) + 'passed + ))) + ) + +(ert-deftest ispell/ispell-comments-and-strings/incorrect () + "Test that `ispell-comments-and-strings' errs +on a correct buffer." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (insert "#!/bin/bash\n" + "echo \"string to check\"\n" + "# tarampampamtararam\n") + (sh-mode) + (ert-with-message-capture lres + (should-error (ispell-comments-and-strings))) + 'passed + )))) + ) + +(ert-deftest ispell/ispell-comment-or-string-at-point () + "Test that `ispell-comment-or-string-at-point' runs two tests. +One correct an one incorrect in the same buffer." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-dictionary nil) + (ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (insert "#!/bin/bash\n" + "echo \"string to check\"\n" + "# tarampampamtararam\n") + (sh-mode) + (goto-char 25) + (ert-with-message-capture lres + (ispell-comment-or-string-at-point) + (should (string-match "Spell-checking .* using .* with .* dict= ionary...done" lres))) + (goto-char 47) + (should-error (ispell-comment-or-string-at-point)) + 'passed)))) + ) + +(ert-deftest ispell/ispell-pdict-save () + "Simple `ispell-pdict-save' test." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (insert "test") + (ispell-kill-ispell t t) + (ispell-pdict-save t t) + 'passed)))) + ) + +(ert-deftest ispell/ispell-pdict-save/force () + "Simple `ispell-pdict-save' test." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (insert "testttttt") + (goto-char 1) + (ispell-kill-ispell t t) + (ispell-pdict-save t t) + 'passed)))) + ) + +(ert-deftest ispell/ispell-pdict-save/modified () + "Simple `ispell-pdict-save' test." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend-available-= p))) + (with-temp-buffer + (insert "testttttt") + (goto-char 1) + (ispell-kill-ispell t t) + (cl-labels ((checker (s) + (should (equal s "#\n")))) + (unwind-protect (progn + (advice-add 'ispell-send-string :override + #'checker) + (let ((ispell-pdict-modified-p t)) + (ispell-pdict-save t nil))) + (advice-remove 'ispell-send-string #'checker)))) + 'passed))) + ) + +(ert-deftest ispell/ispell-pdict-save/unmodified () + "Simple `ispell-pdict-save' test." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (insert "testttttt") + (goto-char 1) + (ispell-kill-ispell t t) + (cl-labels ((checker (_s) + (user-error "test failed"))) + (unwind-protect (progn + (advice-add 'ispell-send-string :override + #'checker) + (let ((ispell-pdict-modified-p nil)) + (ispell-pdict-save t nil))) + (advice-remove 'ispell-send-string #'checker)))) + 'passed))) + ) + +(ert-deftest ispell/ispell-lookup-words/simple () + "Test if `ispell-lookup-words' is runnable." + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory) + (tempfile (make-temp-file "emacs-ispell.el-test" nil nil "waveg= uides"))) + (letopt ((ispell-complete-word-dict tempfile)) + (with-temp-buffer + (insert "waveguid") + (unwind-protect + (progn + (should (equal + (ispell-lookup-words "waveguid") + '("waveguides"))) + (should (equal + (ispell-lookup-words "sdfsdfasdfsadfasdfasdf") + nil))) + (delete-file tempfile))) + 'passed))) + ) + +(ert-deftest ispell/ispell-complete-word/ispell-completion-at-point () + "Test if `ispell-complete-word' and `ispell-completion-at-point' +are runnable." + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory) + (tempfile (make-temp-file "emacs-ispell.el-test" nil nil "waveg= uides"))) + (ignore-errors (ispell-kill-ispell t t)) + (with-ispell-global-dictionary nil + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-complete-word-dict tempfile)) + (with-temp-buffer + (insert "waveguid") + (cl-labels ((my-ispell-command-loop (_p _n _w _s _e) + (car (nth 2 (ispell-completion-at-point))))) + (unwind-protect + (progn + (advice-add 'ispell-command-loop :override + #'my-ispell-command-loop) + (should (equal (car (nth 2 (ispell-completion-at-point= ))) + "waveguides")) + (ispell-complete-word) + (should (equal "waveguides" (buffer-string)))) + (progn + (delete-file tempfile) + (advice-remove + 'ispell-command-loop + #'my-ispell-command-loop))))) + 'passed)))) + ) + +(ert-deftest ispell/ispell-complete-word-interior-frag/simple () + "Test if `ispell-complete-word-interior-frag' is runnable." + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory) + (tempfile (make-temp-file "emacs-ispell.el-test" nil nil "waveg= uides"))) + (with-ispell-global-dictionary nil + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-complete-word-dict tempfile)) + (with-temp-buffer + (insert "waveguid") + (cl-labels ((my-ispell-command-loop (_p _n _w _s _e) + (car (nth 2 (ispell-completion-at-point))))) + (unwind-protect + (progn + (advice-add 'ispell-command-loop :override + #'my-ispell-command-loop) + (goto-char 4) + (ispell-complete-word-interior-frag) + (should (equal "waveguides" (buffer-string)))) + (progn + (delete-file tempfile) + (advice-remove + 'ispell-command-loop + #'my-ispell-command-loop))))) + 'passed)))) + ) + +(ert-deftest ispell/ispell-minor-mode/simple () + "Try enabling `ispell-minor-mode' and test +one test file." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil)) + (with-temp-buffer + (text-mode) + (ispell-minor-mode) + (insert "tarampampamtararam") + (set--this-command-keys " ") + (ert-with-message-capture lres + (ispell-minor-check) + (should (string-match "TARAMPAMPAMTARARAM is incorrect" lres))) + ) + 'passed))) + ) + +(ert-deftest ispell/ispell-message/correct () + "Test that `ispell-message' works. +`ispell-message' is intended to be run before +a message is sent in `message-mode' or `mml-mode'." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend)) + (ispell-dictionary nil)) + (with-temp-buffer + (insert + "To: +Subject: +From: Anon +Fcc: /tmp/234234.cb022f1a625b65b2.mainframe:2,S +User-Agent: mu4e 1.12.9; emacs 31.0.50 +Date: Tue, 09 Sep 2025 07:43:58 +0800 +Message-ID: <878qiov7b5.fsf@mainframe> +--text follows this line-- +Hello World +-- +signature +") + (message-mode) + (ert-with-message-capture lres + (ispell-message) + (should (not (string-match "is incorrect" lres))) + ) + (set-buffer-modified-p nil) + ) + 'passed))) + ) +(ert-deftest ispell/ispell-message/incorrect () + "Test that `ispell-message' works. +`ispell-message' is intended to be run before +a message is sent in `message-mode' or `mml-mode'." + (skip-unless (ispell-tests--some-backend-available-p)) + (with-environment-variables (("HOME" temporary-file-directory)) + (let* ((default-directory temporary-file-directory)) + (letopt ((ispell-program-name (ispell-tests--some-backend))) + (with-temp-buffer + (insert + "To: +Subject: +From: Anon +Fcc: /tmp/234234.cb022f1a625b65b2.mainframe:2,S +User-Agent: mu4e 1.12.9; emacs 31.0.50 +Date: Tue, 09 Sep 2025 07:43:58 +0800 +Message-ID: <878qiov7b5.fsf@mainframe> +--text follows this line-- +<#part sign=3Dpgpmime> +tarampampamtararam +-- +signature +") + (message-mode) + (cl-labels ((checker () + (user-error "expected error"))) + (unwind-protect + (progn + (advice-add 'ispell-show-choices :override #'checker) + (should-error (ispell-message))) + (progn + (advice-remove 'ispell-show-choices #'checker) + (set-buffer-modified-p nil)))) + + ) + 'passed))) + ) + + + +;;=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +;; On this "emacs page" I want to test that customise variables change +;; function behavior in the way are intended to do. + + + (provide 'tests-ispell) ;;; tests-ispell.el ends here --=20 2.46.4 --=-=-= Content-Type: multipart/signed; boundary="==-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" --==-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable =2D-=20 Your sincerely, Vladimir Nikishkin (MiEr, lockywolf) (Laptop) --==-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQSWBebxCAoqaJP8N/j71sGyD+xtqgUCaMDeqwAKCRD71sGyD+xt qskNAP93aaDh2wj3s6AkgIX3zmYzqqXHwZSbWSoI8glG33MjwwD/QAAMDyYGL1rG YuQ5ZArhOqSPsAP3kPWb0B42RyhBmwg= =1u9T -----END PGP SIGNATURE----- --==-=-=-- --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Tue Sep 09 22:43:17 2025 Received: (at 79417) by debbugs.gnu.org; 10 Sep 2025 02:43:17 +0000 Received: from localhost ([127.0.0.1]:34861 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1uwAnj-0006jR-FH for submit@debbugs.gnu.org; Tue, 09 Sep 2025 22:43:17 -0400 Received: from coconut.lockywolf.net ([213.165.252.157]:49030) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1uwAnd-0006jB-Vl for 79417@debbugs.gnu.org; Tue, 09 Sep 2025 22:43:12 -0400 Received: from laptop.lockywolf.net (unknown [IPv6:2408:824e:d2e:8ed0:aafc:415e:4cd2:7b90]) by coconut.lockywolf.net (Postfix) with ESMTPSA id 2200537D8F; Wed, 10 Sep 2025 10:43:05 +0800 (CST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=lockywolf.net; s=2024-10-06; t=1757472187; bh=OiVJxqLLxBdFr4HjDG06slAtlyOLVMAKWfD/aMDmJZk=; h=From:To:Cc:Subject:In-Reply-To:References:Date; b=jj2BijJe1ewaCfBHObe/7evYK78gdBgXBcTr+VVWeLb8xTFX3vxlQCTYIoBtIfRay jsyZrrFI4HYytUFo7/58KLkJivU4ijeeuMO4AooAzjBlSMEPLdCKyuZKtmxt7nzDYn JWUzbz4UFe8D1ortI1/NyfXVNlBtB3kzcWmUN6ATay/mDqtySVr6Rx0KrLKJO1BTTi K01EXxSks8FT28z6JR8WHM4ei27zreVLJMRv7j1EPfr9W1fT6oCRB+Z67aWiKrEqfv RfiiNW+CbdvgaFp2Ri55HI6XFhgLFatEoGN5IO6qc/DirFCfiZuBaN5SV26JBRjtt6 RnnAnrJXXUm1A== From: Lockywolf To: 79417@debbugs.gnu.org. Subject: Re: 31.0.50; Improve ispell.el documentation In-Reply-To: <87ecsft5qu.fsf@laptop.lockywolf.net> References: <87ecsft5qu.fsf@laptop.lockywolf.net> User-Agent: mu4e 1.12.9; emacs 31.0.50 Date: Wed, 10 Sep 2025 10:43:00 +0800 Message-ID: <87jz27rpsb.fsf@laptop.lockywolf.net> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 79417 Cc: Lockywolf X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: multipart/signed; boundary="==-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" --==-=-= Content-Type: text/plain Apologies, the previous patch got mangled, please see the patch attached to this message. --==-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQSWBebxCAoqaJP8N/j71sGyD+xtqgUCaMDltgAKCRD71sGyD+xt qqaQAP9VW2uKy5ua74c2yBbX3Zz6HOARt/wc/ueXaszP18NiIQEAxc5LZOGnr01q rA8Rm6+KwQP5r30GwUevwy4VfW4zRw8= =r1XL -----END PGP SIGNATURE----- --==-=-=-- --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-Improve-ispell-mode-and-flyspell-mode-documentation.patch >From 199ba4f08bf746a233a0bafd7bea62da77b88eea Mon Sep 17 00:00:00 2001 From: Lockywolf Date: Wed, 10 Sep 2025 10:05:31 +0800 Subject: [PATCH] Improve ispell-mode and flyspell-mode documentation. * doc/emacs/fixit.texi (Checking and Correcting Spelling): 1. Fix documentation errors for various ispell-* functions. 2. Document ispell-minor-mode 3. Document ispell word completion functionality 4. Document LocalWords syntax 5. Expand dictionary selection documentation --- doc/emacs/fixit.texi | 468 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 369 insertions(+), 99 deletions(-) diff --git a/doc/emacs/fixit.texi b/doc/emacs/fixit.texi index f240057aa69..17fa71b7def 100644 --- a/doc/emacs/fixit.texi +++ b/doc/emacs/fixit.texi @@ -263,20 +263,222 @@ Spelling @xref{Top, Aspell,, aspell, The Aspell Manual}. @end ifnottex +Note, however, that this section will only help you fix spelling errors, +not grammatical or punctuation errors. + @vindex ispell-program-name +@defvar ispell-program-name +Choose the default spell-checking program by customizing this variable. +@end defvar If you have only one of the spelling checker programs installed, Emacs will find it when you invoke for the first time one of the commands described here. If you have more than one of them installed, you can control which one is used by customizing the variable -@code{ispell-program-name}. +@code{ispell-program-name} or adding the following line into your init +file: @code{(setopt ispell-program-name "hunspell")}. + +@cindex @code{ispell} program +@findex ispell-kill-ispell +@deffn Command ispell-kill-ispell +Kill the spell-checker subprocess. +@end deffn + +Once started, the spell-checker subprocess continues +to run, waiting for something to do, so that subsequent spell-checking +commands complete more quickly. If you want to get rid of the +process, use @w{@kbd{M-x ispell-kill-ispell}}. This is not usually +necessary, since the process uses no processor time except when you do +spelling correction, but might come in handy when the process is used +for the purposes other than interactive spell-checking by externally +installed packages. + +Note that if you use Flyspell-mode for on-the-fly spell-checking +@pxref{flyspell-mode}, and you frequently switch between buffers using +different spell-checking dictionaries, the spell-checking program will +be restarted on each buffer switch, which might be slowing down Emacs if +creating new processes is expensive on your Operating System. + +@subsection Spelling Dictionary Selection +@cindex spell-checking dictionary + +In addition to having the spell checker program installed it is also +important to have spellchecking dictionaries matching the language and +orthography of your preferred writing installed system-wide. You can +check if a dictionary supporting the language you need to have checked +is present by examining the contents of the library directories, usually +printed by the commands in the left column in the following table. The +typical values for GNU/Linux are shown in the right column. +@table @samp +@item @code{ispell -vv} +@code{/usr/lib64/ispell} +@item @code{aspell config} +@code{/usr/lib64/aspell/} +@item Hunspell +@code{/usr/share/hunspell/} +@item Enchant +Uses dictionaries from either Aspell or Hunspell. +@end table +Dictionaries typically consist of two files, one @code{.aff} file with +the grammar, and one @code{.hash} or @code{.dict} with the dictionary. + + Spell-checkers look up spelling in two dictionaries: @emph{the dictionary +installed together with the spell-checker program} (or as a system +add-on) and your @emph{personal dictionary}, which is usually empty when you +have not been using spell-checking for a long time. + +@cindex spell-checking different languages +@cindex language for spell-checking + Usually, a dictionary used by a spell-checker is for a specific +language. The default language is determined from your system's +environment and locale. Both the standard dictionary and your personal +dictionary should be changed if you want to spell-check text in a +different language. You can use the @code{ispell-change-dictionary} +command for that. + +@vindex ispell-dictionary +@vindex ispell-local-dictionary +@findex ispell-change-dictionary +@defopt ispell-dictionary +@defoptx ispell-local-dictionary +The value of this variables shows the current selected spell-checking +dictionary. +@end defopt +@deffn Command ispell-change-dictionary dictionary &optional globalp +Change the spell-checking dictionary (language) for the current buffer. +With a prefix argument change it globally instead. +@end deffn + + The system-wide dictionary (language) used by the spell-checker is +chosen by using the command @w{@kbd{M-x ispell-change-dictionary}}. +When called with a prefix argument or, in the lisp code, with +@code{(ispell-change-dictionary "en_UK" t)}, it sets the value of the +dictionary used for all new buffers saved in the variable +@code{ispell-dictionary}, and without prefix argument it sets it +buffer-locally, setting the variable @code{ispell-local-dictionary}. + + Alternatively, it is possible to override the buffer-local value for +the dictionary by adding @code{"Local IspellDict: en_UK"} at the bottom +of the buffer, within a comment line, and reverting the buffer. This is +useful if the same file has to be shared by multiple developers, some of +which do not use Emacs. This value is usually passed to the spell +checker program using the @code{-d} command-line switch. + +If both @code{ispell-dictionary} and @code{ispell-local-dictionary} are +@code{nil}, that is, if a dictionary has never been changed, the system +will call the spell-checking program without explicitly selecting a +dictionary (language), and the language to be checked will be determined +by the spell-checking program itself. This may be useful if you prefer +to set the language there, so that all client programs using the same +backend spell-checking program would have the same default setting. + +@cindex spell-checking multi-lingual text +@findex ispell-hunspell-add-multi-dic +@deffn Command ispell-hunspell-add-multi-dic +Define a multi-language dictionary to be used with Hunspell. +@end deffn + Hunspell is special in that it supports spell-checking using several +different dictionaries in parallel. To use this feature, invoke the +@w{@kbd{M-x ispell-hunspell-add-multi-dic}} command before you start +using Hunspell for a particular combination of dictionaries. This +command prompts for the dictionary combination, which should be a +comma-separated list of language-specific dictionary names, such as +@samp{en_US,de_DE,ru_RU}. Thereafter, you can spell-check text which +mixes these languages without changing the dictionaries each time. +(Caveat: when several languages use the same script, it is possible that +a word that is mis-spelled in one language is found as a valid spelling +in the dictionary of another language; in that case, the mis-spelled +word might be missed.) + +For example, you can add the following to your Emacs init file: +@lisp +(use-package ispell + ;; uses (setopt ispell-set-spellchecker-params) + (setopt ispell-program-name "hunspell") + (ispell-hunspell-add-multi-dic "en_GB-ise,russian_aot_yo") + (ispell-change-dictionary "en_GB-ise,russian_aot_yo" t)) +@end lisp + +@vindex ispell-personal-dictionary +@defvar ispell-personal-dictionary +The value of this variable is either the name or the full path to the +personal dictionary passed to the spell-checking program using the +@code{-p} command-line switch. +@end defvar + + A ``personal dictionary'' is the file where you can add your own words +to, for example, account for imperfections of the system-wide dictionary +or to avoid less known proper nouns being marked as misspelled. Your +personal dictionary is specified by the variable +@code{ispell-personal-dictionary}. If that is @code{nil}, the spelling +program looks for a personal dictionary in a default location, which is +specific to each spell-checker. In particular Aspell will save the +personal dictionary in @code{~/.aspell..pws}, Ispell uses +@code{~/.ispell_}, but it will also pick up a file with +the same name if it is present in the current directory, so check the +manual to your spell-checking program. A directory-local personal +dictionary may happen to be useful for the case when you are +contributing to a project which uses many domain-specific words which +you might normally consider containing typos. + +@cindex adding spell-checking exceptions +@cindex spell-checking exceptions +@anchor{spell-checking local words} +As opposed to defining a personal dictionary, which would be decoupled +from the file being checked, sometimes it is advantageous to keep +spelling exceptions stored in the same file, especially if the syntax +used in the file allows that. File-local exceptions can be added +interactively when using one of the proofreading commands +(@pxref{Proofreading}), or manually by writing exceptions at the bottom +of the file following the keyword @code{"LocalWords: "}. This allows +preserving the list of exceptions when sending the file over electronic +mail to your collaborators. + +It is also possible to add exceptions for the duration of the running +Emacs session, using the interactive proofreading interface +(@pxref{Proofreading}). These words will be lost after Emacs is +restarted, so you are free to experiment with spelling exceptions +without the fear that a careless addition might end up in a missed typo. + +@subsection Synchronous Spell-Checking (Proofreading) +@cindex proofreading +@cindex spell-checking (synchronous) +@anchor{Proofreading} + +Emacs spell-checking subsystem is originally designed for purposeful +(synchronous) spell-checking, and expects ``writing'' and ``proofing'' +to be two separate activities. If you need less rigorous asynchronous +spell-checking, @pxref{On-the-Fly Spell-Checking}. + +Proofreading in Emacs is usually broken into ``sessions'', which consist +of selecting a piece of text in one way or another (for example, a +region or a whole buffer) and feeding it all (sometimes de-formatted +inside Emacs) to the spell-checking program, prompting the user for +an action when a typo is discovered. + +Throughout the spelling session, the prompted word in question will be +highlighted using the @code{ispell-highlight-face}, which can be +customized. + +@deffn Face ispell-highlight-face +@vindex ispell-highlight-face +@anchor{ispell-highlight-face} +Face to use for highlighting spelling mistakes as identified by +@code{ispell-word} and similar functions. +@end deffn + +@subsubsection Calling the Spell-Checker @table @kbd @item M-$ +@itemx M-x @code{ispell-word} +@anchor{ispell-word} Check and correct spelling of the word at point (@code{ispell-word}). If the region is active, do it for all words in the region instead. +(This is the most frequently used command.) @item C-u M-$ +@itemx M-x @code{ispell-continue} If a previous spelling operation was interrupted, continue that -operation (@code{ispell-continue}). +operation. @item M-x ispell Check and correct spelling of all words in the buffer. If the region is active, do it for all words in the region instead. @@ -286,25 +488,18 @@ Spelling Check and correct spelling in the region. @item M-x ispell-message Check and correct spelling in a draft mail message, excluding cited -material. +material, header fields (@code{To:, From:, Cc:}). @item M-x ispell-comments-and-strings Check and correct spelling of comments and strings in the buffer or region. @item M-x ispell-comment-or-string-at-point Check the comment or string at point. -@item M-x ispell-change-dictionary @key{RET} @var{dict} @key{RET} -Restart the spell-checker process, using @var{dict} as the dictionary. -@item M-x ispell-kill-ispell -Kill the spell-checker subprocess. -@item M-@key{TAB} -@itemx @key{ESC} @key{TAB} -@itemx C-M-i -Complete the word before point based on the spelling dictionary and -other completion sources (@code{completion-at-point}). -@item M-x flyspell-mode -Enable Flyspell mode, which highlights all misspelled words. -@item M-x flyspell-prog-mode -Enable Flyspell mode for comments and strings only. @end table +@defvar ispell-check-comments +@vindex ispell-check-comments +Adjust the aforementioned commands' behaviour in modes based on +@code{text-mode}, but using a lot of structured markup, such as +@code{org-mode}. +@end defvar @kindex M-$ @findex ispell-word @@ -325,18 +520,30 @@ Spelling @findex ispell-comments-and-strings @findex ispell-comment-or-string-at-point @cindex spell-checking the active region - Similarly, the command @kbd{M-x ispell} performs spell-checking in -the region if one is active, or in the entire buffer otherwise. The + Similarly, the command @kbd{M-x ispell} performs spell-checking in the +region if one is active, or in the entire buffer otherwise. The commands @w{@kbd{M-x ispell-buffer}} and @w{@kbd{M-x ispell-region}} explicitly perform spell-checking on the entire buffer or the region respectively. To check spelling in an email message you are writing, use @w{@kbd{M-x ispell-message}}; that command checks the whole buffer, -except for material that is indented or appears to be cited from other -messages. @xref{Sending Mail}. When dealing with source code, you +except for material that is indented, appears to be cited from other +messages, or belongs to the email message formal syntax (@code{To:, +From:, Cc:}). @xref{Sending Mail}. When dealing with source code, you can use @kbd{M-x ispell-comments-and-strings} or @w{@kbd{M-x ispell-comment-or-string-at-point}} to check only comments or string literals. + By default in modes based on @code{text-mode}, the aforementioned +commands will perform spell-checking on all of the text, even if is +commented-out. Sometimes, however, you might want to comment-out pieces +of structural markup, such as @code{org-mode}'s tags, which might not be +proper words, but due to being commented-out are indistinguishable from +normal comment text. To prevent checking of comments in text modes, set +@code{ispell-check-comments} to @code{nil}. Setting it to +@code{'exclusive} will make the spell-checker check @emph{only} comments. + +@subsubsection Correcting Spelling Mistakes +@cindex spell-checking (correcting mistakes) When one of these commands encounters what appears to be an incorrect word, it asks you what to do. It usually displays a list of numbered @dfn{near-misses}---words that are close to the incorrect word. @@ -355,7 +562,8 @@ Spelling @item r @var{new} @key{RET} Replace the word, just this time, with @var{new}. (The replacement -string will be rescanned for more spelling errors.) +string will be rescanned for more spelling errors, thus marking other +of the occurrences of the same spelling as correct.) @item R @var{new} @key{RET} Replace the word with @var{new}, and do a @code{query-replace} so you @@ -368,11 +576,13 @@ Spelling @item A Accept the incorrect word---treat it as correct, but only in this -editing session and for this buffer. +file. (This adds @code{LocalWords: } syntax to the file footer +(@pxref{spell-checking local words}).) @item i -Insert this word in your personal dictionary file so that it will be -considered correct from now on, even in future sessions. +Insert this word in your personal dictionary file (the spell-checking +program does the insertion) so that it will be considered correct from +now on, even in future sessions and even outside Emacs. @item m Like @kbd{i}, but you can also specify dictionary completion @@ -383,10 +593,10 @@ Spelling file. @item l @var{word} @key{RET} -Look in the dictionary for words that match @var{word}. These words -become the new list of near-misses; you can select one of them as -the replacement by typing a digit. You can use @samp{*} in @var{word} as a -wildcard. +Look up the words that match @var{word} in the completion dictionary +(@pxref{plain-text completion}). These words become the new list of +near-misses; you can select one of them as the replacement by typing a +digit. You can use @samp{*} in @var{word} as a wildcard. @item C-g @itemx X @@ -416,6 +626,51 @@ Spelling @item ? Show the list of options. +@end table + +@subsection Plain-Text Word Completion +@cindex completion (plain-text) +@cindex completion (words) +@cindex word completion +@anchor{plain-text completion} +Emacs also supports words completion based on a simple plain-text list +of words separated by newlines, with no support for morphology. Despite +being quite primitive, this mechanism is often the only way of +assistance a text editor can provide for the user writing a text without +spending a lot of computational resources on syntactic and semantic +analysis of the text. + +@vindex ispell-complete-word-dict +@defvar ispell-complete-word-dict +@defvarx ispell-alternate-dictionary + Primary and fallback dictionary for plain-text completion. +@end defvar + + A separate (from @code{ispell-dictionary} and +@code{ispell-local-dictionary}) dictionary is used for plain-text word +completion. The variable @code{ispell-complete-word-dict} specifies the +file name of this dictionary. The completion dictionary must be +different because it cannot use the information about roots and affixes +of the words, which spell-checking uses to detect variations of words. +For some languages, there is a spell-checking dictionary but no word +completion dictionary. The variable @code{ispell-alternate-dictionary} +serves the same purpose, but points to the system-wide dictionary, which +is usually pre-installed by your Operating System vendor. On GNU/Linux +this is often a wordlist extracted from the Webster dictionary (with +definitions removed), shipped with some older editions of UNIX, +installed at @code{/usr/share/dict/words}. The ``GNU Miscfiles'' +project provides a (relatively limited) sample of such a wordlist if +necessary. + +@table @kbd +@item M-@key{TAB} +@itemx @key{ESC} @key{TAB} +@itemx C-M-i +@itemx @kbd{Tools -> Spell Checking -> Complete Word} +@itemx @kbd{Tools -> Spell Checking -> Complete Word Fragment} +Complete the word before point based on the spelling dictionary and +other completion sources (@code{completion-at-point}, +@code{ispell-complete-word}, @code{ispell-complete-word-interior-frag}). @end table Use the command @kbd{M-@key{TAB}} (@code{completion-at-point}) to @@ -424,68 +679,25 @@ Spelling window manager intercepts @kbd{M-@key{TAB}}, type @w{@kbd{@key{ESC} @key{TAB}}} or @kbd{C-M-i}.) -@cindex @code{ispell} program -@findex ispell-kill-ispell - Once started, the spell-checker subprocess continues -to run, waiting for something to do, so that subsequent spell-checking -commands complete more quickly. If you want to get rid of the -process, use @w{@kbd{M-x ispell-kill-ispell}}. This is not usually -necessary, since the process uses no processor time except when you do -spelling correction. - -@vindex ispell-dictionary -@vindex ispell-local-dictionary -@vindex ispell-personal-dictionary -@findex ispell-change-dictionary - Spell-checkers look up spelling in two dictionaries: -the standard dictionary and your personal dictionary. The standard -dictionary is specified by the variable @code{ispell-local-dictionary} -or, if that is @code{nil}, by the variable @code{ispell-dictionary}. -If both are @code{nil}, the spelling program's default dictionary is -used. The command @w{@kbd{M-x ispell-change-dictionary}} sets the -standard dictionary for the buffer and then restarts the subprocess, -so that it will use a different standard dictionary. Your personal -dictionary is specified by the variable -@code{ispell-personal-dictionary}. If that is @code{nil}, the -spelling program looks for a personal dictionary in a default -location, which is specific to each spell-checker. - -@cindex spell-checking different languages -@cindex language for spell-checking - Usually, a dictionary used by a spell-checker is for a specific -language. The default language is determined from your system's -environment and locale. Both the standard dictionary and your personal -dictionary should be changed if you want to spell-check text in a -different language. You can use the @code{ispell-change-dictionary} -command for that. - -@cindex spell-checking multi-lingual text -@findex ispell-hunspell-add-multi-dic - Hunspell is special in that it supports spell-checking using several -different dictionaries in parallel. To use this feature, invoke the -@kbd{M-x ispell-hunspell-add-multi-dic} command before you start using -Hunspell for a particular combination of dictionaries. This command -prompts for the dictionary combination, which should be a -comma-separated list of language-specific dictionary names, such as -@samp{en_US,de_DE,ru_RU}. Thereafter, you can spell-check text which -mixes these languages without changing the dictionaries each time. -(Caveat: when several languages use the same script, it is possible that -a word that is mis-spelled in one language is found as a valid spelling -in the dictionary of another language; in that case, the mis-spelled -word might be missed.) -@vindex ispell-complete-word-dict - A separate dictionary is used for word completion. The variable -@code{ispell-complete-word-dict} specifies the file name of this -dictionary. The completion dictionary must be different because it -cannot use the information about roots and affixes of the words, which -spell-checking uses to detect variations of words. For some -languages, there is a spell-checking dictionary but no word completion -dictionary. +@subsection Asynchronous (On-the-Fly) Spell-Checking +@cindex on-the-fly spell-checking +@cindex spell-checking (on-the-fly) +@anchor{On-the-Fly Spell-Checking} @cindex Flyspell mode @cindex mode, Flyspell @findex flyspell-mode +@anchor{flyspell-mode} +@table @kbd +@item M-x flyspell-mode +Enable Flyspell mode, which highlights all misspelled words. (Most +useful in modes based on @code{text-mode}.) + +Note that @code{flyspell-mode}, when enabled, will change the value of +@code{ispell-highlight-face} (@pxref{ispell-highlight-face}). +@end table + Flyspell mode is a minor mode that performs automatic spell-checking of the text you type as you type it. When it finds a word that it does not recognize, it highlights that word. Type @w{@kbd{M-x @@ -493,31 +705,89 @@ Spelling enable Flyspell mode in all text mode buffers, add @code{flyspell-mode} to @code{text-mode-hook}. @xref{Hooks}. Note that, as Flyspell mode needs to check each word across which you move, -it will slow down cursor motion and scrolling commands. It also -doesn't automatically check the text you didn't type or move across; -use @code{flyspell-region} or @code{flyspell-buffer} for that. +it will slow down cursor motion and scrolling commands. @vindex flyspell-check-changes - Normally, Flyspell mode highlights misspelled words that you typed or -modified, but also words you move across without changing them. But if -you customize the variable @code{flyspell-check-changes} to a +@defvar flyspell-check-changes +Enable spell-checking words you move across. +@end defvar +@deffn Face flyspell-error +@deffnx Face flyspell-duplicate +@vindex flyspell-error +@vindex flyspell-duplicate +These two faces define how the spelling mistake will be highlighted +by @code{flyspell-mode}, the first being used for highlighting first +occurrences of errors, and the second used for all subsequent identical +misspellings. These will override @code{ispell-highlight-face}. +@end deffn +@deffn Command flyspell-region +@deffnx Command flyspell-buffer +Highlight spelling mistakes in a region or in a buffer. +@end deffn + Normally, Flyspell mode highlights both misspelled words that you +typed or modified, and also words you move across without changing them. +But if you customize the variable @code{flyspell-check-changes} to a non-@code{nil} value, Flyspell mode will check only the words you typed -or edited in some way. +or edited in some way. It @emph{doesn't automatically check the text you +didn't type or move across}; use @code{flyspell-region} or +@code{flyspell-buffer} for that. + @findex flyspell-correct-word -@findex flyspell-auto-correct-word @findex flyspell-correct-word-before-point +@findex flyspell-auto-correct-word +@table @kbd +@item @kbd{mouse-2} +@itemx @code{(flyspell-correct-word)} +Display a menu of possible correction using mouse. +@item @kbd{C-c $} +@itemx @code{(flyspell-correct-word-before-point)} +Display a menu of possible corrections using keyboard. +@item @kbd{M-@key{TAB}} +@itemx @kbd{C-M-i} +@itemx @kbd{@key{ESC} @key{TAB}} +@itemx @kbd{C-.} +@itemx (@code{flyspell-auto-correct-word}) +Automatically correct word before point. +@end table When Flyspell mode highlights a word as misspelled, you can click on it with @kbd{mouse-2} (@code{flyspell-correct-word}) to display a menu of possible corrections and actions. If you want this menu on -@kbd{mouse-3} instead, enable @code{context-menu-mode}. In addition, -@kbd{C-.} or @kbd{@key{ESC} @key{TAB}} (@code{flyspell-auto-correct-word}) -will propose various successive corrections for the word at point, and -@w{@kbd{C-c $}} (@code{flyspell-correct-word-before-point}) will pop -up a menu of possible corrections. Of course, you can always correct -the misspelled word by editing it manually in any way you like. +@kbd{mouse-3} instead, enable @code{context-menu-mode}. Note, however, +that @code{flyspell-mode} needs to be enable @emph{after} +@code{context-menu-mode}, because it will @emph{redefine} the context +menu already installed there by other modes. In addition, @kbd{C-.} or +@kbd{@key{ESC} @key{TAB}} (@code{flyspell-auto-correct-word}) will +propose various successive corrections for the word at point, and +@w{@kbd{C-c $}} (@code{flyspell-correct-word-before-point}) will pop up +a menu of possible corrections. Of course, you can always correct the +misspelled word by editing it manually in any way you like, or use +@w{@kbd{M-$}} @pxref{ispell-word}. + +@cindex Ispell Minor Mode +@cindex mode, Ispell Minor +@anchor{ispell-minor-mode} +@findex ispell-minor-mode +@table @kbd +@item M-x ispell-minor-mode +Enable spell-checking of the last typed word only. +@end table + +If @code{flyspell-mode} proves to be too burdensome for your computer, +or if it is producing too many false positives and visually clutters +your display, @code{ispell-minor-mode} is a much more lightweight +alternative, which only checks the last word as you are typing it, and +shows a brief message in the echo area. +@cindex Flyspell Prog mode +@cindex mode, Flyspell Prog +@anchor{flyspell-prog-mode} @findex flyspell-prog-mode +@table @kbd +@item M-x flyspell-prog-mode +Enable Flyspell mode for comments and strings only. (Most useful for +modes based on @code{prog-mode}.) +@end table Flyspell Prog mode works just like ordinary Flyspell mode, except that it only checks words in comments and string constants. This feature is useful for editing programs. Type @w{@kbd{M-x -- 2.46.4 --=-=-= Content-Type: text/plain -- Your sincerely, Vladimir Nikishkin (MiEr, lockywolf) (Laptop) --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Wed Sep 10 09:26:17 2025 Received: (at 79417) by debbugs.gnu.org; 10 Sep 2025 13:26:17 +0000 Received: from localhost ([127.0.0.1]:37379 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1uwKq1-00083H-4E for submit@debbugs.gnu.org; Wed, 10 Sep 2025 09:26:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:46806) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1uwKpx-00082x-9D for 79417@debbugs.gnu.org; Wed, 10 Sep 2025 09:26:13 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1uwKpr-0006MD-ES; Wed, 10 Sep 2025 09:26:07 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=Pz0LdGg51BymK+PeO3ZO8HQw80LcNnWCv8ytcsfAo9Y=; b=l0jfwfxBkmfK PczwZcNV+LgNDZ5r2HH6iISnTkn4LxkQaraA0T2R1x9Foui4CrA02hUFd8eojNGpBOqKfMAWmhRHw dNAX4vM8wuKRyI3iSMmPJLczWwaXWYXCXWXAUgIDUtCALCXsL6ZXSF8UlirB3OwBm+deDRaJILYkB h5yotCYGeouWaPNlrwxZUJZWB9hGBtRXS2QXyL9HZ328zq/YaCHopZSkN9vHdphiJJOUcI8rZVyDj +9CYRpC+KzLk7GjIAsmMsg2jyaaE+cPVenKHF1yc+LLLGjxSw63dCrm/NjbZK2Nv/vW708qli16H2 mVkrZpFW2QZ2/uNXzmGf0Q==; Date: Wed, 10 Sep 2025 16:25:58 +0300 Message-Id: <86ikhqbfrt.fsf@gnu.org> From: Eli Zaretskii To: Lockywolf In-Reply-To: <87jz27rpsb.fsf@laptop.lockywolf.net> (message from Lockywolf on Wed, 10 Sep 2025 10:43:00 +0800) Subject: Re: bug#79417: 31.0.50; Improve ispell.el documentation References: <87ecsft5qu.fsf@laptop.lockywolf.net> <87jz27rpsb.fsf@laptop.lockywolf.net> X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 79417 Cc: 79417@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) > Cc: Lockywolf > From: Lockywolf > Date: Wed, 10 Sep 2025 10:43:00 +0800 > > Apologies, the previous patch got mangled, please see the patch attached > to this message. Thanks, but this is too massive an addition for it to be accepted as-is. The "Spelling" node is already 200+ lines long, and your changes add 250 lines more. This is too much for these features, which are relatively minor, as Emacs features go. Would you agree to make the patch smaller, only mention important features and issues, and describe those as succinctly as possible, leaving the rest to the doc strings of the relevant commands and variables? For example, why do we need to expand the documentation of ispell-kill-ispell beyond what it already says? And why do we need to describe how and in which directories to install dictionaries -- people should either a distro or, if they know what they are doing, install the dictionaries themselves using the documentation provided with the speller; we don't want to track changes in those installations and update our manual each time they change. Or why expand the description of local and personal dictionaries so much -- does the existing text lack some important information? Same question about selecting dictionaries for non-default languages. Or why describe the Ispell faces in the manual? Or the complete new section about word-completion using ispell -- is that really so important to warrant so much text? And I'm not sure I understand the need for so much reshuffling of the existing text. Perhaps if we don't extend existing descriptions so much, the need to move the text around so much will also disappear? I also don't like using @subsections that have no nodes: they make the manual harder to navigate. If we must introduce new subsections, let's make each one of them a separate node. May I suggest that, instead of posting a jumbo patch, you post a list of problems you see with important details and aspects of spell-checking, as they are currently documented (or not documented) in the manual, and we could then discuss these one by one and decide whether and how each one of them should be fixed? If nothing else, it will allow us to discuss the problems first, rather than start from a full-blown solution. Thanks. From debbugs-submit-bounces@debbugs.gnu.org Wed Sep 10 21:52:53 2025 Received: (at 79417) by debbugs.gnu.org; 11 Sep 2025 01:52:53 +0000 Received: from localhost ([127.0.0.1]:41205 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1uwWUT-0006nA-PA for submit@debbugs.gnu.org; Wed, 10 Sep 2025 21:52:53 -0400 Received: from coconut.lockywolf.net ([213.165.252.157]:37246) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1uwWUE-0006mg-Me for 79417@debbugs.gnu.org; Wed, 10 Sep 2025 21:52:44 -0400 Received: from laptop.lockywolf.net (unknown [IPv6:2408:824e:d2e:8ed0:3cf7:f217:ceff:d8a4]) by coconut.lockywolf.net (Postfix) with ESMTPSA id E171E38BF9; Thu, 11 Sep 2025 09:52:30 +0800 (CST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=lockywolf.net; s=2024-10-06; t=1757555553; bh=zAjAXO9hHLyGfJRVVhDqfFx5x/OL3pW4pcqZBaY42YI=; h=From:To:Cc:Subject:In-Reply-To:References:Date; b=AY6b0G9XJ96R62Ayd+QqyqujTTfYIjFltNNRj2IIgvVMuW03h91kU3LZX0S5ZH+O9 UIPHAmbAVPAVhMXPG16DdYXSwRTnGOCBn0t2YwU2N82/AW8kpbRG4+YLxdM0UY/TTA 5X7KH3MuPwR9xwDbxNkaww6bWZZp/C6onOPzj0RlxOOZuXKUrGYiARS/YWJMG47m8W TC4VjRNLPjHVVAa6WymWZRXOlS7qTvgALT/c7wR6UwOuwCojhn2DbCJPHAXhgoDIrG R+hTEMYBGCyE/ok1izAoVnHPFOgUj/Ag3CM8C+DfovpUaeZebwvFFW8odlpU49kgQ1 ZTI10U0KPUySA== From: Lockywolf To: Eli Zaretskii Subject: Re: bug#79417: 31.0.50; Improve ispell.el documentation In-Reply-To: <86ikhqbfrt.fsf@gnu.org> References: <87ecsft5qu.fsf@laptop.lockywolf.net> <87jz27rpsb.fsf@laptop.lockywolf.net> <86ikhqbfrt.fsf@gnu.org> User-Agent: mu4e 1.12.9; emacs 31.0.50 Date: Thu, 11 Sep 2025 09:52:03 +0800 Message-ID: <87cy7xhi2k.fsf@laptop.lockywolf.net> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 79417 Cc: 79417@debbugs.gnu.org, Lockywolf X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: multipart/signed; boundary="==-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" --==-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable Eli Zaretskii writes: > Thanks, but this is too massive an addition for it to be accepted > as-is. The "Spelling" node is already 200+ lines long, and your > changes add 250 lines more. Let us adjust it to make it acceptable then! Personally I embarked on the job of implementing ispell.el tests with the goal of writing good documentation, because I found the existing one insufficient. For years I failed to make ispell.el do what I expected a spell-checker module to do. As we can see, melpa has several packages essentially duplicating ispell.el's (+flyspell) functionality, such as "jinx", or "spell-fu", or "flycheck-ispell", largely because people, seemingly, failed to understand how to use ispell.el. >This is too much for these features, > which are relatively minor, as Emacs features go. While the feature itself is not large, it adds a lot of convenience if implemented properly and a lot of frustration if it does not do what is natural and expected. I also think that alleged simplicity of the feature is a mis-perception, because human languages vastly exceed formal ones in complexity. Moreover, Emacs is not just used by people editing programs, it is also widely used by documentation writers and publishers, for whom a customizable and flexible spell-checker is one of the first things they are looking for in a text editor. > Would you agree to make the patch smaller, only mention important > features and issues, and describe those as succinctly as possible, > leaving the rest to the doc strings of the relevant commands and > variables? I am quite happy to make the patch smaller, as long as it remains being easily readable by people opening it while using Emacs for the first time. A spell-checker is such a commonplace feature that users are often asking for it on the very first day. > For example, why do we need to expand the documentation of > ispell-kill-ispell beyond what it already says? Firstly, I don think that there should be a need for a public ispell-kill-ispell command, so I would be happy to delete this part altogether. It should be called ispell--kill-ispell, and only be used internally. But, unfortunately, the user, at the moment, needs to be aware of it, because in a very common scenario the notification area is full with messages "Ispell process killed" and "Starting new Ispell process with ... dictionary". The scenario is the following: the user has two buffers open, one editing text in language A, another editing text in language B, and both have flyspell enabled. This is not just confusing, it also noticeably slows down buffer switching. This is probably not possible to avoid, because otherwise Emacs would have to keep a running spell-checker process per buffer, which is too much, or keep a pool of processes for the most recently used buffers, which is complicated, but in any case, this is not the only use-case for making the user aware of the fact that "some kind of background process is running and you might have to kill it manually if something goes wrong". Of course, it would have been much better if this function could be entirely removed from the public interface and the two start/kill messages be hidden from *Messages* unless some kind of a debug switch is enabled, but doing that is hard, and having one paragraph in the manual clariflying the issue seems like a small price to pay. > And why do we need to > describe how and in which directories to install dictionaries -- > people should either a distro or, if they know what they are doing, > install the dictionaries themselves using the documentation provided > with the speller;=20 Because dictionaries are given to the user as completions in the M-x change-ispell-dictionary command, which takes them from the ispell-dictionary-base-alist variable, which contains dictionaries which are not installed. This leads to a very confusing behaviour: the user is suggested to use, say, a "british" dictionary, whereas, in fact such a dicionary does not exist, and ispell.el generates errors. The situation becomes even more confusing, because other engines might actually have the "british" dictionary, so setting "british" with International Ispell works (because it is in the ispell-dictionary-base-alist) while using it fails, but with aspell setting the "british" dictionary fails (because there is no such dictionary in "aspell dicts") while actually using it (setf ispell-dictionary "british" without ispell-change-dictionary) works, because aspell is smart enough choose an english dictionary when called like "aspell -d british -a". Dictionary choice is a huge source of confusion, that is why I spend so much text explaining it. >we don't want to track changes in those > installations and update our manual each time they change. We are already doing that with the variable ispell-dictionary-base-alist, and the other code to get dictionary lists from the backends. And, of course, it is de-synchronised with the upsteam of the spell-checker programs. In my experience, explaining it in the manual with a necessary warning "things may change, examine the spell-checker documentation" is better than trying to be overly too smart in the code. After all, if the user just gives us the path to the correct dictionary, and we have to avoid extracting that info from the backends, it reduces the amount of heuristics in our code and makes things simpler. The user is doing it just once, and we have to ensure and test the work of ispell.el in various environments and different circumstances. > Or why > expand the description of local and personal dictionaries so much -- > does the existing text lack some important information? Yes. When I was reading it the first time, I thought that "ispell-dictionary" is the dictionary shipped by the OS vendor, and the "ispell-personal-dictionary" is the dictionary I have to purchase elsewhere. For example, ispell-dictionary would be the systemwide English, and ispell-personal-dictionary would be the dictionary for Russian I could buy from, say, Abbyy or PROMT. In fact, ispell-personal-dictionary is not even a dictionary, it is a wordlist. It is also not immediately obvious whether it is better to have a personal dictionary per language (or per the spell-checking program), or the same personal dictionary for all dictionaries (or spell-checking programs). Moreover, the information about "Local Words" as a great alternative to a personal dictionary is entirely missing from the manual. > Same question > about selecting dictionaries for non-default languages. This is very important. Firstly, the information about the "Local IspellDict:" keyword is entirely missing from the present manual, even though it is a very useful tool. There is a huge confusion with the local dictionary variable. When both ispell-dictionary and ispell-local-dictionary are nil, ispell would use the so-called "default" dictionary, that is call the spell-checker program without the "-d" switch, which is fine in many cases. Now assume your ispell-dictionary is fr_FR, and your ispell-local-dictionary is ru_RU, and you call M-x ispell-change-dictionary RET nil RET. You would expect that ispell be called with a "default" dictionary, without -d switch, but, in fact, the opposite happens, ispell is called with -d fr_FR. This issue would not have appeared if ispell-dictionary would just become buffer-local when set, but this is not the case. The original design of ispell.el expects this issue to be solved by the "Local IspellDict: " keyword most of the time, but the information about it is missing from the manual. > Or why > describe the Ispell faces in the manual? Well, I added it because flyspell-mode changes the ispell.el highlight face from default "isearch" to "flyspell-error". This is hugely confusing, and I wanted to explain to the readers why this happens. And documenting this behaviour for flyspell-mode, I found myself referring to ispell-highlight-face, therefore needing to document it as well. If this behaviour is removed, ispell-highlight-face and flyspell-error become completely disjoint, this elaboration is unnecessary and can be removed. > Or the complete new section > about word-completion using ispell -- is that really so important to > warrant so much text? Yes, it is very important. At the moment, ispell.el has five dictionary defcustoms: ispell-dictionary, ispell-local-dictionary, ispell-personal-dictionary, ispell-complete-word-dict, and ispell-alternate-dictionary, and asome of them can become buffer-local. This is VERY confusing for a user, and deserves a thorough explanation. Completion functionality, while itself being very simple, appears in a lot of places: in the ispell menu, in CAPF functions, in hippie-expansion, and various third-party packages, such as company-mode, various completion frameworks, which are too numerous to enumerate. So, suppose, in the simplest default case, I am editing a file written in French, and invoke some completion command: my word is completed in Spanish. Bah! I made a mistake, my ispell-complete-word-dict is set to a Spanish wordlist. I run (setq-local ispell-complete-word-dict nil), and retry completion. Ah, what is going on, my word is completed in English, because ispell-alternate-dictionary is pointing to the Webster /usr/share/dict/words, which I didn't even know existed. It is better to be safe than sorry and to tell the user precisely what is going on. > And I'm not sure I understand the need for so much reshuffling of the > existing text. Perhaps if we don't extend existing descriptions so > much, the need to move the text around so much will also disappear? The current narrative of the manual starts from a seldom-expected nowadays feature of querying the backend to correct a word. This is not what most people expect from a spell-checking tool in 2025. First and foremost people expect to have their typos highlighted. Most people never even use the corrective feature, preferring to re-write the word themselves instead. However, using (asynchronous) flyspell-mode in Emacs is impossible without understanding the logic of synchronous spell-checking of ispell.el. Therefore, I tried to re-structure the narrative of the section in a layered way: more advanced features, building on top of simpler features, come later. Hence, the structure is the following: 1. Set up the engine. This is impossible to avoid, for any of the use-cases covered by the section. This section is short. 2. Set up the dictionaries. This is extremely confusing, and without properly set up dictionaries nothing else works, so it is better to give this subsubsection to the user first, while they is not yet tired and capable of reading attentively. 2a. In fact, the user may already stop here, if they are using some other front-end, not flyspell-mode or ispell.el's interactive features. 3. Proofreading section describes the actual paradigm of working with UNIX spellcheckers as backends. Of course it requires the engine and the dictionary to be configured correctly, but does not require flyspell-mode, hence comes earlier. It is also quite in handy to the users of flyspell, because, even though flyspell-mode has its own correction features, those open a GUI menu, which are not that convenient to use, so I suspect that most people would still use M-$ to open the ispell.el's corrections buffer, so it is better to introduce it earlier. 4. The Completion section describes a part of ispell.el, but is not as important as the Proofreading part, and hence comes later as it is a dedicated subsubsection, it can be skipped, and the user can go to the flyspell-mode directly. 5. Flyspell-mode is the most high-level layer, with asynchronous process running, and GUI menus involved, hence comes last. Now that I am writing it, I am realising that the Prooofreading functionality calls the Completion functionality (the "l" key in the interactive buffer), but not the other way round. It might be better to make the Completion subsubsection to come earlier. > I also don't like using @subsections that have no nodes: they make the > manual harder to navigate. If we must introduce new subsections, > let's make each one of them a separate node. No problem. New patch attached (nodes added, but faces not yet removed). --==-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQSWBebxCAoqaJP8N/j71sGyD+xtqgUCaMIrWQAKCRD71sGyD+xt qjHTAQCgJs4/wL+a3wAMDmvPOqsgS/T8wQU/0lG8B4U2aZ3NsQEA3l5TufIEsiFQ WixzY0sJ1pcDrL1AIj6iq1NotwvEjwc= =+7pK -----END PGP SIGNATURE----- --==-=-=-- --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-Improve-ispell-mode-and-flyspell-mode-documentation.patch >From 59085ac9abb11809cb17ac70aba53d5cda6dd418 Mon Sep 17 00:00:00 2001 From: Lockywolf Date: Wed, 10 Sep 2025 10:05:31 +0800 Subject: [PATCH] Improve ispell-mode and flyspell-mode documentation. * doc/emacs/fixit.texi (Checking and Correcting Spelling): 1. Fix documentation errors for various ispell-* functions. 2. Document ispell-minor-mode 3. Document ispell word completion functionality 4. Document LocalWords syntax 5. Expand dictionary selection documentation --- doc/emacs/fixit.texi | 473 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 373 insertions(+), 100 deletions(-) diff --git a/doc/emacs/fixit.texi b/doc/emacs/fixit.texi index f240057aa69..924075f4a60 100644 --- a/doc/emacs/fixit.texi +++ b/doc/emacs/fixit.texi @@ -263,20 +263,224 @@ Spelling @xref{Top, Aspell,, aspell, The Aspell Manual}. @end ifnottex +Note, however, that this section will only help you fix spelling errors, +not grammatical or punctuation errors. + @vindex ispell-program-name +@defvar ispell-program-name +Choose the default spell-checking program by customizing this variable. +@end defvar If you have only one of the spelling checker programs installed, Emacs will find it when you invoke for the first time one of the commands described here. If you have more than one of them installed, you can control which one is used by customizing the variable -@code{ispell-program-name}. +@code{ispell-program-name} or adding the following line into your init +file: @code{(setopt ispell-program-name "hunspell")}. + +@cindex @code{ispell} program +@findex ispell-kill-ispell +@deffn Command ispell-kill-ispell +Kill the spell-checker subprocess. +@end deffn + +Once started, the spell-checker subprocess continues +to run, waiting for something to do, so that subsequent spell-checking +commands complete more quickly. If you want to get rid of the +process, use @w{@kbd{M-x ispell-kill-ispell}}. This is not usually +necessary, since the process uses no processor time except when you do +spelling correction, but might come in handy when the process is used +for the purposes other than interactive spell-checking by externally +installed packages. + +Note that if you use Flyspell-mode for on-the-fly spell-checking +@pxref{flyspell-mode}, and you frequently switch between buffers using +different spell-checking dictionaries, the spell-checking program will +be restarted on each buffer switch, which might be slowing down Emacs if +creating new processes is expensive on your Operating System. + +@node Dictionary Selection +@subsection Spelling Dictionary Selection +@cindex spell-checking dictionary + +In addition to having the spell checker program installed it is also +important to have spellchecking dictionaries matching the language and +orthography of your preferred writing installed system-wide. You can +check if a dictionary supporting the language you need to have checked +is present by examining the contents of the library directories, usually +printed by the commands in the left column in the following table. The +typical values for GNU/Linux are shown in the right column. +@table @samp +@item @code{ispell -vv} +@code{/usr/lib64/ispell} +@item @code{aspell config} +@code{/usr/lib64/aspell/} +@item Hunspell +@code{/usr/share/hunspell/} +@item Enchant +Uses dictionaries from either Aspell or Hunspell. +@end table +Dictionaries typically consist of two files, one @code{.aff} file with +the grammar, and one @code{.hash} or @code{.dict} with the dictionary. + + Spell-checkers look up spelling in two dictionaries: @emph{the dictionary +installed together with the spell-checker program} (or as a system +add-on) and your @emph{personal dictionary}, which is usually empty when you +have not been using spell-checking for a long time. + +@cindex spell-checking different languages +@cindex language for spell-checking + Usually, a dictionary used by a spell-checker is for a specific +language. The default language is determined from your system's +environment and locale. Both the standard dictionary and your personal +dictionary should be changed if you want to spell-check text in a +different language. You can use the @code{ispell-change-dictionary} +command for that. + +@vindex ispell-dictionary +@vindex ispell-local-dictionary +@findex ispell-change-dictionary +@defopt ispell-dictionary +@defoptx ispell-local-dictionary +The value of this variables shows the current selected spell-checking +dictionary. +@end defopt +@deffn Command ispell-change-dictionary dictionary &optional globalp +Change the spell-checking dictionary (language) for the current buffer. +With a prefix argument change it globally instead. +@end deffn + + The system-wide dictionary (language) used by the spell-checker is +chosen by using the command @w{@kbd{M-x ispell-change-dictionary}}. +When called with a prefix argument or, in the lisp code, with +@code{(ispell-change-dictionary "en_UK" t)}, it sets the value of the +dictionary used for all new buffers saved in the variable +@code{ispell-dictionary}, and without prefix argument it sets it +buffer-locally, setting the variable @code{ispell-local-dictionary}. + + Alternatively, it is possible to override the buffer-local value for +the dictionary by adding @code{"Local IspellDict: en_UK"} at the bottom +of the buffer, within a comment line, and reverting the buffer. This is +useful if the same file has to be shared by multiple developers, some of +which do not use Emacs. This value is usually passed to the spell +checker program using the @code{-d} command-line switch. + +If both @code{ispell-dictionary} and @code{ispell-local-dictionary} are +@code{nil}, that is, if a dictionary has never been changed, the system +will call the spell-checking program without explicitly selecting a +dictionary (language), and the language to be checked will be determined +by the spell-checking program itself. This may be useful if you prefer +to set the language there, so that all client programs using the same +backend spell-checking program would have the same default setting. + +@cindex spell-checking multi-lingual text +@findex ispell-hunspell-add-multi-dic +@deffn Command ispell-hunspell-add-multi-dic +Define a multi-language dictionary to be used with Hunspell. +@end deffn + Hunspell is special in that it supports spell-checking using several +different dictionaries in parallel. To use this feature, invoke the +@w{@kbd{M-x ispell-hunspell-add-multi-dic}} command before you start +using Hunspell for a particular combination of dictionaries. This +command prompts for the dictionary combination, which should be a +comma-separated list of language-specific dictionary names, such as +@samp{en_US,de_DE,ru_RU}. Thereafter, you can spell-check text which +mixes these languages without changing the dictionaries each time. +(Caveat: when several languages use the same script, it is possible that +a word that is mis-spelled in one language is found as a valid spelling +in the dictionary of another language; in that case, the mis-spelled +word might be missed.) + +For example, you can add the following to your Emacs init file: +@lisp +(use-package ispell + ;; uses (setopt ispell-set-spellchecker-params) + (setopt ispell-program-name "hunspell") + (ispell-hunspell-add-multi-dic "en_GB-ise,russian_aot_yo") + (ispell-change-dictionary "en_GB-ise,russian_aot_yo" t)) +@end lisp + +@vindex ispell-personal-dictionary +@defvar ispell-personal-dictionary +The value of this variable is either the name or the full path to the +personal dictionary passed to the spell-checking program using the +@code{-p} command-line switch. +@end defvar + + A ``personal dictionary'' is the file where you can add your own words +to, for example, account for imperfections of the system-wide dictionary +or to avoid less known proper nouns being marked as misspelled. Your +personal dictionary is specified by the variable +@code{ispell-personal-dictionary}. If that is @code{nil}, the spelling +program looks for a personal dictionary in a default location, which is +specific to each spell-checker. In particular Aspell will save the +personal dictionary in @code{~/.aspell..pws}, Ispell uses +@code{~/.ispell_}, but it will also pick up a file with +the same name if it is present in the current directory, so check the +manual to your spell-checking program. A directory-local personal +dictionary may happen to be useful for the case when you are +contributing to a project which uses many domain-specific words which +you might normally consider containing typos. + +@cindex adding spell-checking exceptions +@cindex spell-checking exceptions +@anchor{spell-checking local words} +As opposed to defining a personal dictionary, which would be decoupled +from the file being checked, sometimes it is advantageous to keep +spelling exceptions stored in the same file, especially if the syntax +used in the file allows that. File-local exceptions can be added +interactively when using one of the proofreading commands +(@pxref{Proofreading}), or manually by writing exceptions at the bottom +of the file following the keyword @code{"LocalWords: "}. This allows +preserving the list of exceptions when sending the file over electronic +mail to your collaborators. + +It is also possible to add exceptions for the duration of the running +Emacs session, using the interactive proofreading interface +(@pxref{Proofreading}). These words will be lost after Emacs is +restarted, so you are free to experiment with spelling exceptions +without the fear that a careless addition might end up in a missed typo. + +@node Proofreading +@subsection Synchronous Spell-Checking (Proofreading) +@cindex proofreading +@cindex spell-checking (synchronous) +@anchor{Proofreading} + +Emacs spell-checking subsystem is originally designed for purposeful +(synchronous) spell-checking, and expects ``writing'' and ``proofing'' +to be two separate activities. If you need less rigorous asynchronous +spell-checking, @pxref{On-the-Fly Spell-Checking}. + +Proofreading in Emacs is usually broken into ``sessions'', which consist +of selecting a piece of text in one way or another (for example, a +region or a whole buffer) and feeding it all (sometimes de-formatted +inside Emacs) to the spell-checking program, prompting the user for +an action when a typo is discovered. + +Throughout the spelling session, the prompted word in question will be +highlighted using the @code{ispell-highlight-face}, which can be +customized. + +@deffn Face ispell-highlight-face +@vindex ispell-highlight-face +@anchor{ispell-highlight-face} +Face to use for highlighting spelling mistakes as identified by +@code{ispell-word} and similar functions. +@end deffn + +@subsubsection Calling the Spell-Checker @table @kbd @item M-$ +@itemx M-x @code{ispell-word} +@anchor{ispell-word} Check and correct spelling of the word at point (@code{ispell-word}). If the region is active, do it for all words in the region instead. +(This is the most frequently used command.) @item C-u M-$ +@itemx M-x @code{ispell-continue} If a previous spelling operation was interrupted, continue that -operation (@code{ispell-continue}). +operation. @item M-x ispell Check and correct spelling of all words in the buffer. If the region is active, do it for all words in the region instead. @@ -286,25 +490,18 @@ Spelling Check and correct spelling in the region. @item M-x ispell-message Check and correct spelling in a draft mail message, excluding cited -material. +material, header fields (@code{To:, From:, Cc:}). @item M-x ispell-comments-and-strings Check and correct spelling of comments and strings in the buffer or region. @item M-x ispell-comment-or-string-at-point Check the comment or string at point. -@item M-x ispell-change-dictionary @key{RET} @var{dict} @key{RET} -Restart the spell-checker process, using @var{dict} as the dictionary. -@item M-x ispell-kill-ispell -Kill the spell-checker subprocess. -@item M-@key{TAB} -@itemx @key{ESC} @key{TAB} -@itemx C-M-i -Complete the word before point based on the spelling dictionary and -other completion sources (@code{completion-at-point}). -@item M-x flyspell-mode -Enable Flyspell mode, which highlights all misspelled words. -@item M-x flyspell-prog-mode -Enable Flyspell mode for comments and strings only. @end table +@defvar ispell-check-comments +@vindex ispell-check-comments +Adjust the aforementioned commands' behaviour in modes based on +@code{text-mode}, but using a lot of structured markup, such as +@code{org-mode}. +@end defvar @kindex M-$ @findex ispell-word @@ -325,18 +522,30 @@ Spelling @findex ispell-comments-and-strings @findex ispell-comment-or-string-at-point @cindex spell-checking the active region - Similarly, the command @kbd{M-x ispell} performs spell-checking in -the region if one is active, or in the entire buffer otherwise. The + Similarly, the command @kbd{M-x ispell} performs spell-checking in the +region if one is active, or in the entire buffer otherwise. The commands @w{@kbd{M-x ispell-buffer}} and @w{@kbd{M-x ispell-region}} explicitly perform spell-checking on the entire buffer or the region respectively. To check spelling in an email message you are writing, use @w{@kbd{M-x ispell-message}}; that command checks the whole buffer, -except for material that is indented or appears to be cited from other -messages. @xref{Sending Mail}. When dealing with source code, you +except for material that is indented, appears to be cited from other +messages, or belongs to the email message formal syntax (@code{To:, +From:, Cc:}). @xref{Sending Mail}. When dealing with source code, you can use @kbd{M-x ispell-comments-and-strings} or @w{@kbd{M-x ispell-comment-or-string-at-point}} to check only comments or string literals. + By default in modes based on @code{text-mode}, the aforementioned +commands will perform spell-checking on all of the text, even if is +commented-out. Sometimes, however, you might want to comment-out pieces +of structural markup, such as @code{org-mode}'s tags, which might not be +proper words, but due to being commented-out are indistinguishable from +normal comment text. To prevent checking of comments in text modes, set +@code{ispell-check-comments} to @code{nil}. Setting it to +@code{'exclusive} will make the spell-checker check @emph{only} comments. + +@subsubsection Correcting Spelling Mistakes +@cindex spell-checking (correcting mistakes) When one of these commands encounters what appears to be an incorrect word, it asks you what to do. It usually displays a list of numbered @dfn{near-misses}---words that are close to the incorrect word. @@ -355,7 +564,8 @@ Spelling @item r @var{new} @key{RET} Replace the word, just this time, with @var{new}. (The replacement -string will be rescanned for more spelling errors.) +string will be rescanned for more spelling errors, thus marking other +of the occurrences of the same spelling as correct.) @item R @var{new} @key{RET} Replace the word with @var{new}, and do a @code{query-replace} so you @@ -368,11 +578,13 @@ Spelling @item A Accept the incorrect word---treat it as correct, but only in this -editing session and for this buffer. +file. (This adds @code{LocalWords: } syntax to the file footer +(@pxref{spell-checking local words}).) @item i -Insert this word in your personal dictionary file so that it will be -considered correct from now on, even in future sessions. +Insert this word in your personal dictionary file (the spell-checking +program does the insertion) so that it will be considered correct from +now on, even in future sessions and even outside Emacs. @item m Like @kbd{i}, but you can also specify dictionary completion @@ -383,10 +595,10 @@ Spelling file. @item l @var{word} @key{RET} -Look in the dictionary for words that match @var{word}. These words -become the new list of near-misses; you can select one of them as -the replacement by typing a digit. You can use @samp{*} in @var{word} as a -wildcard. +Look up the words that match @var{word} in the completion dictionary +(@pxref{plain-text completion}). These words become the new list of +near-misses; you can select one of them as the replacement by typing a +digit. You can use @samp{*} in @var{word} as a wildcard. @item C-g @itemx X @@ -416,6 +628,52 @@ Spelling @item ? Show the list of options. +@end table + +@node Plain-Text Completion +@subsection Plain-Text Word Completion +@cindex completion (plain-text) +@cindex completion (words) +@cindex word completion +@anchor{plain-text completion} +Emacs also supports words completion based on a simple plain-text list +of words separated by newlines, with no support for morphology. Despite +being quite primitive, this mechanism is often the only way of +assistance a text editor can provide for the user writing a text without +spending a lot of computational resources on syntactic and semantic +analysis of the text. + +@vindex ispell-complete-word-dict +@defvar ispell-complete-word-dict +@defvarx ispell-alternate-dictionary + Primary and fallback dictionary for plain-text completion. +@end defvar + + A separate (from @code{ispell-dictionary} and +@code{ispell-local-dictionary}) dictionary is used for plain-text word +completion. The variable @code{ispell-complete-word-dict} specifies the +file name of this dictionary. The completion dictionary must be +different because it cannot use the information about roots and affixes +of the words, which spell-checking uses to detect variations of words. +For some languages, there is a spell-checking dictionary but no word +completion dictionary. The variable @code{ispell-alternate-dictionary} +serves the same purpose, but points to the system-wide dictionary, which +is usually pre-installed by your Operating System vendor. On GNU/Linux +this is often a wordlist extracted from the Webster dictionary (with +definitions removed), shipped with some older editions of UNIX, +installed at @code{/usr/share/dict/words}. The ``GNU Miscfiles'' +project provides a (relatively limited) sample of such a wordlist if +necessary. + +@table @kbd +@item M-@key{TAB} +@itemx @key{ESC} @key{TAB} +@itemx C-M-i +@itemx @kbd{Tools -> Spell Checking -> Complete Word} +@itemx @kbd{Tools -> Spell Checking -> Complete Word Fragment} +Complete the word before point based on the spelling dictionary and +other completion sources (@code{completion-at-point}, +@code{ispell-complete-word}, @code{ispell-complete-word-interior-frag}). @end table Use the command @kbd{M-@key{TAB}} (@code{completion-at-point}) to @@ -424,68 +682,25 @@ Spelling window manager intercepts @kbd{M-@key{TAB}}, type @w{@kbd{@key{ESC} @key{TAB}}} or @kbd{C-M-i}.) -@cindex @code{ispell} program -@findex ispell-kill-ispell - Once started, the spell-checker subprocess continues -to run, waiting for something to do, so that subsequent spell-checking -commands complete more quickly. If you want to get rid of the -process, use @w{@kbd{M-x ispell-kill-ispell}}. This is not usually -necessary, since the process uses no processor time except when you do -spelling correction. - -@vindex ispell-dictionary -@vindex ispell-local-dictionary -@vindex ispell-personal-dictionary -@findex ispell-change-dictionary - Spell-checkers look up spelling in two dictionaries: -the standard dictionary and your personal dictionary. The standard -dictionary is specified by the variable @code{ispell-local-dictionary} -or, if that is @code{nil}, by the variable @code{ispell-dictionary}. -If both are @code{nil}, the spelling program's default dictionary is -used. The command @w{@kbd{M-x ispell-change-dictionary}} sets the -standard dictionary for the buffer and then restarts the subprocess, -so that it will use a different standard dictionary. Your personal -dictionary is specified by the variable -@code{ispell-personal-dictionary}. If that is @code{nil}, the -spelling program looks for a personal dictionary in a default -location, which is specific to each spell-checker. - -@cindex spell-checking different languages -@cindex language for spell-checking - Usually, a dictionary used by a spell-checker is for a specific -language. The default language is determined from your system's -environment and locale. Both the standard dictionary and your personal -dictionary should be changed if you want to spell-check text in a -different language. You can use the @code{ispell-change-dictionary} -command for that. - -@cindex spell-checking multi-lingual text -@findex ispell-hunspell-add-multi-dic - Hunspell is special in that it supports spell-checking using several -different dictionaries in parallel. To use this feature, invoke the -@kbd{M-x ispell-hunspell-add-multi-dic} command before you start using -Hunspell for a particular combination of dictionaries. This command -prompts for the dictionary combination, which should be a -comma-separated list of language-specific dictionary names, such as -@samp{en_US,de_DE,ru_RU}. Thereafter, you can spell-check text which -mixes these languages without changing the dictionaries each time. -(Caveat: when several languages use the same script, it is possible that -a word that is mis-spelled in one language is found as a valid spelling -in the dictionary of another language; in that case, the mis-spelled -word might be missed.) - -@vindex ispell-complete-word-dict - A separate dictionary is used for word completion. The variable -@code{ispell-complete-word-dict} specifies the file name of this -dictionary. The completion dictionary must be different because it -cannot use the information about roots and affixes of the words, which -spell-checking uses to detect variations of words. For some -languages, there is a spell-checking dictionary but no word completion -dictionary. +@node Asynchronous Spell-Checking +@subsection Asynchronous (On-the-Fly) Spell-Checking +@cindex on-the-fly spell-checking +@cindex spell-checking (on-the-fly) +@anchor{On-the-Fly Spell-Checking} @cindex Flyspell mode @cindex mode, Flyspell @findex flyspell-mode +@anchor{flyspell-mode} +@table @kbd +@item M-x flyspell-mode +Enable Flyspell mode, which highlights all misspelled words. (Most +useful in modes based on @code{text-mode}.) + +Note that @code{flyspell-mode}, when enabled, will change the value of +@code{ispell-highlight-face} (@pxref{ispell-highlight-face}). +@end table + Flyspell mode is a minor mode that performs automatic spell-checking of the text you type as you type it. When it finds a word that it does not recognize, it highlights that word. Type @w{@kbd{M-x @@ -493,31 +708,89 @@ Spelling enable Flyspell mode in all text mode buffers, add @code{flyspell-mode} to @code{text-mode-hook}. @xref{Hooks}. Note that, as Flyspell mode needs to check each word across which you move, -it will slow down cursor motion and scrolling commands. It also -doesn't automatically check the text you didn't type or move across; -use @code{flyspell-region} or @code{flyspell-buffer} for that. +it will slow down cursor motion and scrolling commands. @vindex flyspell-check-changes - Normally, Flyspell mode highlights misspelled words that you typed or -modified, but also words you move across without changing them. But if -you customize the variable @code{flyspell-check-changes} to a +@defvar flyspell-check-changes +Enable spell-checking words you move across. +@end defvar +@deffn Face flyspell-error +@deffnx Face flyspell-duplicate +@vindex flyspell-error +@vindex flyspell-duplicate +These two faces define how the spelling mistake will be highlighted +by @code{flyspell-mode}, the first being used for highlighting first +occurrences of errors, and the second used for all subsequent identical +misspellings. These will override @code{ispell-highlight-face}. +@end deffn +@deffn Command flyspell-region +@deffnx Command flyspell-buffer +Highlight spelling mistakes in a region or in a buffer. +@end deffn + Normally, Flyspell mode highlights both misspelled words that you +typed or modified, and also words you move across without changing them. +But if you customize the variable @code{flyspell-check-changes} to a non-@code{nil} value, Flyspell mode will check only the words you typed -or edited in some way. +or edited in some way. It @emph{doesn't automatically check the text you +didn't type or move across}; use @code{flyspell-region} or +@code{flyspell-buffer} for that. + @findex flyspell-correct-word -@findex flyspell-auto-correct-word @findex flyspell-correct-word-before-point +@findex flyspell-auto-correct-word +@table @kbd +@item @kbd{mouse-2} +@itemx @code{(flyspell-correct-word)} +Display a menu of possible correction using mouse. +@item @kbd{C-c $} +@itemx @code{(flyspell-correct-word-before-point)} +Display a menu of possible corrections using keyboard. +@item @kbd{M-@key{TAB}} +@itemx @kbd{C-M-i} +@itemx @kbd{@key{ESC} @key{TAB}} +@itemx @kbd{C-.} +@itemx (@code{flyspell-auto-correct-word}) +Automatically correct word before point. +@end table When Flyspell mode highlights a word as misspelled, you can click on it with @kbd{mouse-2} (@code{flyspell-correct-word}) to display a menu of possible corrections and actions. If you want this menu on -@kbd{mouse-3} instead, enable @code{context-menu-mode}. In addition, -@kbd{C-.} or @kbd{@key{ESC} @key{TAB}} (@code{flyspell-auto-correct-word}) -will propose various successive corrections for the word at point, and -@w{@kbd{C-c $}} (@code{flyspell-correct-word-before-point}) will pop -up a menu of possible corrections. Of course, you can always correct -the misspelled word by editing it manually in any way you like. +@kbd{mouse-3} instead, enable @code{context-menu-mode}. Note, however, +that @code{flyspell-mode} needs to be enable @emph{after} +@code{context-menu-mode}, because it will @emph{redefine} the context +menu already installed there by other modes. In addition, @kbd{C-.} or +@kbd{@key{ESC} @key{TAB}} (@code{flyspell-auto-correct-word}) will +propose various successive corrections for the word at point, and +@w{@kbd{C-c $}} (@code{flyspell-correct-word-before-point}) will pop up +a menu of possible corrections. Of course, you can always correct the +misspelled word by editing it manually in any way you like, or use +@w{@kbd{M-$}} @pxref{ispell-word}. + +@cindex Ispell Minor Mode +@cindex mode, Ispell Minor +@anchor{ispell-minor-mode} +@findex ispell-minor-mode +@table @kbd +@item M-x ispell-minor-mode +Enable spell-checking of the last typed word only. +@end table + +If @code{flyspell-mode} proves to be too burdensome for your computer, +or if it is producing too many false positives and visually clutters +your display, @code{ispell-minor-mode} is a much more lightweight +alternative, which only checks the last word as you are typing it, and +shows a brief message in the echo area. +@cindex Flyspell Prog mode +@cindex mode, Flyspell Prog +@anchor{flyspell-prog-mode} @findex flyspell-prog-mode +@table @kbd +@item M-x flyspell-prog-mode +Enable Flyspell mode for comments and strings only. (Most useful for +modes based on @code{prog-mode}.) +@end table Flyspell Prog mode works just like ordinary Flyspell mode, except that it only checks words in comments and string constants. This feature is useful for editing programs. Type @w{@kbd{M-x -- 2.46.4 --=-=-= Content-Type: text/plain > May I suggest that, instead of posting a jumbo patch, you post a list > of problems you see with important details and aspects of > spell-checking, as they are currently documented (or not documented) > in the manual, and we could then discuss these one by one and decide > whether and how each one of them should be fixed? If nothing else, it > will allow us to discuss the problems first, rather than start from a > full-blown solution. I never thought about this patch as "jumbo". As you said, it's just 250 lines, less than a single function of ispell.el (ispell-command-loop is 279 lines, ispell-region is 153 lines), and is less than 5% of ispell.el+flyspell.el, which are 4345+2411=6756 lines long (5096 without comments and empty lines). After all, the whole documentation to Elisp ratio for Emacs is find lisp -iname '*.el' -exec cat {} + | \ grep --binary-files=text -v '^;' | \ grep --binary-files=text -v '^$' | wc -l 1671287 find doc -iname '*.texi' -exec cat {} + | \ grep --binary-files=text -v '^%' | \ grep --binary-files=text -v '^$' | wc -l 354929 echo '354929/1671287' | bc -l .21 So, roughly speaking ispell.el's documentation is 4 times terser than Emacs' on average. Even if you add all of the C code (which is much sparser documented) without removing comments: find src lisp \( -iname '*.el' -or -iname '*.c' \) -exec cat {} + | \ grep --binary-files=text -v '^;' | \ grep --binary-files=text -v '^$' | wc -l 2110316 echo '354929/2110316' | bc -l .16 This is still three times more elaborate and detailed than the spelling documentation in fixit.texi -- Your sincerely, Vladimir Nikishkin (MiEr, lockywolf) (Laptop) --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Thu Sep 11 02:05:59 2025 Received: (at 79417) by debbugs.gnu.org; 11 Sep 2025 06:05:59 +0000 Received: from localhost ([127.0.0.1]:41817 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1uwaRR-0008Tm-NI for submit@debbugs.gnu.org; Thu, 11 Sep 2025 02:05:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:50732) by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1uwaRP-0008TO-0K for 79417@debbugs.gnu.org; Thu, 11 Sep 2025 02:05:56 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1uwaRJ-00011z-4E; Thu, 11 Sep 2025 02:05:49 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=References:Subject:In-Reply-To:To:From:Date: mime-version; bh=HE+8U5fjuxVqq+XGcNVaXJpvw91OOvgXexaNoUdfvfQ=; b=Bs3jgfXjpfyu LT/Xe3qihMCFF0IF6gsG/WdR6OfzS/uFv1rhZneIbD3cJqdsIYkesg/eLkglCzhFQ0CT6NJsj+yrD xYptmqXi+1niAAUV8W1/62xVp24TeXLYAEvXfyA05Xu3jXXAFcbJAwXOLTci1+rC2VrryCJN8rkVW blloJS7yzMH4QcH8edarv35V3M0b3lVuSG90gW+EkgC6R9LbrqnUPr09+1WOFlKwBjwfLX40QjyIu MIq6N1XUqUQxYJgbc4qnwdcXqqp74I7tAQ2e+JvZzEgO/8eXwyHuTfxOiooSWZ3ocCU1zyiwFm4vh xNwHaZ5dN/dxN5FgH1kQ1g==; Date: Thu, 11 Sep 2025 09:05:13 +0300 Message-Id: <86a531v812.fsf@gnu.org> From: Eli Zaretskii To: Lockywolf In-Reply-To: <87cy7xhi2k.fsf@laptop.lockywolf.net> (message from Lockywolf on Thu, 11 Sep 2025 09:52:03 +0800) Subject: Re: bug#79417: 31.0.50; Improve ispell.el documentation References: <87ecsft5qu.fsf@laptop.lockywolf.net> <87jz27rpsb.fsf@laptop.lockywolf.net> <86ikhqbfrt.fsf@gnu.org> <87cy7xhi2k.fsf@laptop.lockywolf.net> X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 79417 Cc: 79417@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) > From: Lockywolf > Cc: Lockywolf , 79417@debbugs.gnu.org > Date: Thu, 11 Sep 2025 09:52:03 +0800 > > > For example, why do we need to expand the documentation of > > ispell-kill-ispell beyond what it already says? > > Firstly, I don think that there should be a need for a public > ispell-kill-ispell command, so I would be happy to delete this part > altogether. It should be called ispell--kill-ispell, and only be used > internally. But, unfortunately, the user, at the moment, needs to be > aware of it, because in a very common scenario the notification area is > full with messages "Ispell process killed" and "Starting new Ispell > process with ... dictionary". The scenario is the following: the user > has two buffers open, one editing text in language A, another editing > text in language B, and both have flyspell enabled. This is not just > confusing, it also noticeably slows down buffer switching. This is > probably not possible to avoid, because otherwise Emacs would have to > keep a running spell-checker process per buffer, which is too much, or > keep a pool of processes for the most recently used buffers, which is > complicated, but in any case, this is not the only use-case for making > the user aware of the fact that "some kind of background process is > running and you might have to kill it manually if something goes wrong". > > Of course, it would have been much better if this function could be > entirely removed from the public interface and the two start/kill > messages be hidden from *Messages* unless some kind of a debug switch is > enabled, but doing that is hard, and having one paragraph in the manual > clariflying the issue seems like a small price to pay. If the problem is the messages emitted by ispell-kill-ispell, we could make it do its job silently when invoked non-interactively. Would that solve the problem, and allow us to leave the description of ispell-kill-ispell in the manual alone? (We cannot easily remove or rename what has been an interactive command for so long, but if the problem is the messages, I don't think removal or renaming is necessary.) We could also make ispell-change-dictionary be silent when invoked non-interactively, although in this case I'd think the message is useful (so maybe we should have a user option to disable it, for those who are annoyed). IOW, this doesn't sound like a documentation problem. > > And why do we need to > > describe how and in which directories to install dictionaries -- > > people should either a distro or, if they know what they are doing, > > install the dictionaries themselves using the documentation provided > > with the speller; > > Because dictionaries are given to the user as completions in the > M-x change-ispell-dictionary command, which takes them from the > ispell-dictionary-base-alist variable, which contains dictionaries which > are not installed. This leads to a very confusing behaviour: the user is > suggested to use, say, a "british" dictionary, whereas, in fact such > a dicionary does not exist, and ispell.el generates errors. If that is the problem, let's change ispell-change-dictionary not to suggest dictionaries that aren't installed, or show something like "(not installed)" after dictionaries that are not installed. This is not a documentation problem, either. > Dictionary choice is a huge source of confusion, that is why I spend so > much text explaining it. If our commands cause confusion, we should fix those commands, not explain their confusing operation. > >we don't want to track changes in those > > installations and update our manual each time they change. > > We are already doing that with the variable > ispell-dictionary-base-alist, and the other code to get dictionary lists > from the backends. With any backend but Ispell, ispell-dictionary-base-alist is filled by querying the speller. This is exactly what we should do, and it doesn't entail stating the results in the manual. The speller will always correctly tell Emacs what are the dictionaries, so asking it doesn't add any maintenance burden for us. Having the directories in the manual _is_ a maintenance burden, and I'd like to avoid it. If we can do something similar with the recent versions of Ispell, let's do it (in the code). In any case, concluding from this that we need to describe in the manual the directories where dictionaries are installed is IMO wrong. > And, of course, it is de-synchronised with the > upsteam of the spell-checker programs. In my experience, explaining it > in the manual with a necessary warning "things may change, examine the > spell-checker documentation" is better than trying to be overly too > smart in the code. After all, if the user just gives us the path to the > correct dictionary, and we have to avoid extracting that info from the > backends, it reduces the amount of heuristics in our code and makes > things simpler. The user is doing it just once, and we have to ensure > and test the work of ispell.el in various environments and different > circumstances. Users should not need to specify the directories where dictionaries are installed, they should only specify the name of the dictionary to use. The code in ispell.el should be able to find the directory. AFAIK, with all the backends except Ispell, it already does so; if there are problems with that, let's fix the code to eliminate those problems. > > Or why > > expand the description of local and personal dictionaries so much -- > > does the existing text lack some important information? > > Yes. When I was reading it the first time, I thought that > "ispell-dictionary" is the dictionary shipped by the OS vendor, and the > "ispell-personal-dictionary" is the dictionary I have to purchase > elsewhere. For example, ispell-dictionary would be the systemwide > English, and ispell-personal-dictionary would be the dictionary for > Russian I could buy from, say, Abbyy or PROMT. Maybe we should just say that the personal dictionary adds the user's personal words to the standard dictionary. Would that be sufficient? We could say that in the doc string of ispell-personal-dictionary. > In fact, ispell-personal-dictionary is not even a dictionary, it is a > wordlist. This is an implementation detail, not really relevant to users. We don't (and shouldn't) explain how the standard dictionary is implemented, so neither should we explain how personal dictionaries are implemented. > It is also not immediately obvious whether it is better to have a > personal dictionary per language (or per the spell-checking program), or > the same personal dictionary for all dictionaries (or spell-checking > programs). The default is spell-checker dependent, AFAIK. Hunspell, for example, creates a personal dictionary for each language. If necessary for people who customize this option, we can recommend in the doc string to use separate values for different languages (although this is not convenient if one changes languages frequently, and thus best left to the backend). In any case, it should be enough to document this in the doc string. > Moreover, the information about "Local Words" as a great alternative to > a personal dictionary is entirely missing from the manual. We could add that, but the description should be much shorter. All that's needed is a mention of the local variable and its usage by ispell.e. > > Same question > > about selecting dictionaries for non-default languages. > > This is very important. Firstly, the information about the "Local > IspellDict:" keyword is entirely missing from the present manual, even > though it is a very useful tool. Again, a single sentence should be enough. > There is a huge confusion with the local dictionary variable. > > When both ispell-dictionary and ispell-local-dictionary are nil, ispell > would use the so-called "default" dictionary, that is call the > spell-checker program without the "-d" switch, which is fine in many > cases. > > Now assume your ispell-dictionary is fr_FR, and your > ispell-local-dictionary is ru_RU, and you call M-x > ispell-change-dictionary RET nil RET. It doesn't make much sense to do this, but again, the place to document what this special case does is in the doc string, not in the manual. FWIW, I spell-check in several languages, but never invoke ispell-change-dictionary with the argument of nil. > > Or why > > describe the Ispell faces in the manual? > > Well, I added it because flyspell-mode changes the ispell.el highlight > face from default "isearch" to "flyspell-error". This is > hugely confusing, and I wanted to explain to the readers why this > happens. And documenting this behaviour for flyspell-mode, I found > myself referring to ispell-highlight-face, therefore needing to document > it as well. I see no reason to have this in the manual. Not everything that has relevance to spell-checking must be in the manual: we have other documentation facilities in Emacs that can and should be used by someone who studies the spell-checking capabilities. > If this behaviour is removed, ispell-highlight-face and flyspell-error > become completely disjoint, this elaboration is unnecessary and can be > removed. They can be found by apropos commands and customize-group. We do this with most (all?) of the other faces. > > Or the complete new section > > about word-completion using ispell -- is that really so important to > > warrant so much text? > > Yes, it is very important. > > At the moment, ispell.el has five dictionary defcustoms: > ispell-dictionary, ispell-local-dictionary, ispell-personal-dictionary, > ispell-complete-word-dict, and ispell-alternate-dictionary, and asome of > them can become buffer-local. > > This is VERY confusing for a user, and deserves a thorough explanation. Why is that confusing? If their doc strings do a poor job, let's improve those doc strings. > Completion functionality, while itself being very simple, appears in a > lot of places: in the ispell menu, in CAPF functions, in > hippie-expansion, and various third-party packages, such as > company-mode, various completion frameworks, which are too numerous to > enumerate. > > So, suppose, in the simplest default case, I am editing a file written > in French, and invoke some completion command: my word is completed in > Spanish. Bah! I made a mistake, my ispell-complete-word-dict is set to a > Spanish wordlist. I run (setq-local ispell-complete-word-dict nil), and > retry completion. Ah, what is going on, my word is completed in English, > because ispell-alternate-dictionary is pointing to the Webster > /usr/share/dict/words, which I didn't even know existed. > > It is better to be safe than sorry and to tell the user precisely what > is going on. Let's first see how the related doc strings can be improved, and take it from there. I'm not yet convinced we need to dedicate so much text to word completion. The completion command(s) (which you didn't name in the above, btw) should tell in their doc strings which dictionaries and word lists they use, and in what order. If they don't, let's first improve that, instead of putting that burden on the manual. > > I also don't like using @subsections that have no nodes: they make the > > manual harder to navigate. If we must introduce new subsections, > > let's make each one of them a separate node. > > No problem. New patch attached (nodes added, but faces not yet removed). I suggest first to discuss an agree on what should and shouldn't be in the manual, and what should be fixed in other places (code and doc strings). Only after we have an agreement, it would make sense to post a patch according to the agreements. Thanks.