* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
@ 2015-06-05 14:06 Reuben Thomas
2015-06-05 14:08 ` bug#20741: Workaround Reuben Thomas
2015-06-05 19:23 ` bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period Eli Zaretskii
0 siblings, 2 replies; 26+ messages in thread
From: Reuben Thomas @ 2015-06-05 14:06 UTC (permalink / raw)
To: 20741
flyspell marks as incorrect “etc.”, “i.e.”, “e.g.” &c.
flyspell is of course behaving as expected: “.” is in OTHERCHARS, and as
it comes after the word, it is not included.
ispell sets my default dictionary to en_GB (from my locale, I presume),
and I’m using hunspell.
If I run ispell-buffer on a buffer containing the above words, they
pass, which is surprising in that it seems that the OTHERCHARS
specification has not been applied in this case. It is not surprising in
the sense that these definitions are in my dictionary.
The somewhat nonsensical result is that if I run ispell-word on such a
word marked incorrect by flyspell, the first correction offered is the
word I already have, plus a period. If I select it, the net effect is
that an extra period is inserted, and flyspell complains again.
I tried to move “.” to CASECHARS and NOT-CASECHARS in a custom
dictionary definition:
("en_GB" "[[:alpha:].]" "[^[:alpha:].]" "['0-9’-]" t
("-d" "en_GB")
nil utf-8)
but this causes flyspell to give an error saying it got nil where it
expected a stringp in its post-command-hook. In any case, I guess this
would not do what I wanted without adding an inflexion rule to the
dictionary that allowed any word to add “.” (except, ideally, a word
that already ends in a period).
In GNU Emacs 24.4.1 (x86_64-pc-linux-gnu, GTK+ Version 3.10.8)
of 2014-11-21 on skwd, modified by Debian
Windowing system distributor `The X.Org Foundation', version 11.0.11501000
System Description: Ubuntu 14.04.2 LTS
Configured using:
`configure --build x86_64-linux-gnu --prefix=/usr
--sharedstatedir=/var/lib --libexecdir=/usr/lib
--localstatedir=/var/lib --infodir=/usr/share/info
--mandir=/usr/share/man --with-pop=yes
--enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.4/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.4/site-lisp:/usr/share/emacs/site-lisp
--build x86_64-linux-gnu --prefix=/usr --sharedstatedir=/var/lib
--libexecdir=/usr/lib --localstatedir=/var/lib
--infodir=/usr/share/info --mandir=/usr/share/man --with-pop=yes
--enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.4/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.4/site-lisp:/usr/share/emacs/site-lisp
--with-x=yes --with-x-toolkit=gtk3 --with-toolkit-scroll-bars
'CFLAGS=-g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat
-Werror=format-security -Wall' CPPFLAGS=-D_FORTIFY_SOURCE=2
'LDFLAGS=-Wl,-Bsymbolic-functions -Wl,-z,relro''
Important settings:
value of $LC_MONETARY: en_GB.UTF-8
value of $LC_NUMERIC: en_GB.UTF-8
value of $LC_TIME: en_GB.UTF-8
value of $LANG: en_GB.UTF-8
value of $XMODIFIERS: @im=local
locale-coding-system: utf-8-unix
Major mode: Emacs-Lisp
Minor modes in effect:
TeX-PDF-mode: t
TeX-source-correlate-mode: t
shell-dirtrack-mode: t
paredit-mode: t
show-paren-mode: t
savehist-mode: t
minibuffer-electric-default-mode: t
icomplete-mode: t
global-auto-revert-mode: t
desktop-save-mode: t
bug-reference-prog-mode: t
global-undo-tree-mode: t
undo-tree-mode: t
global-whitespace-mode: t
ido-everywhere: t
dtrt-indent-mode: t
global-auto-complete-mode: t
auto-complete-mode: t
eldoc-mode: t
tooltip-mode: t
electric-indent-mode: t
mouse-wheel-mode: t
file-name-shadow-mode: t
global-font-lock-mode: t
font-lock-mode: t
blink-cursor-mode: t
auto-composition-mode: t
auto-encryption-mode: t
auto-compression-mode: t
column-number-mode: t
line-number-mode: t
transient-mark-mode: (only . t)
Recent input:
<switch-frame> C-x b A g <tab> <return> C-n C-n C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-f C-f C-f C-f C-f C-f C-f
C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f
C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f
C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f
C-f C-f C-f C-f C-f C-f C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-p C-p C-p C-p C-p C-p C-p
C-p C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b
C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-b C-p
C-b C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f C-f
C-x b * N <backspace> M e s s <tab> <return> C-r f
g g l C-a C-x b <return> C-x b c u s t <tab> <return>
C-a <switch-frame> <switch-frame> <switch-frame> <help-echo>
<switch-frame> <down-mouse-1> <mouse-movement> <mouse-movement>
<drag-mouse-1> M-x r e p o r t - e m a c s - b u g
<return>
Recent messages:
Applying style hooks... done
Applying style hooks... done
Applying style hooks... done
Applying style hooks... done
Wrote /home/rrt/.emacs.desktop.lock
Desktop: 4 frames, 16 buffers restored.
For information about GNU Emacs and the GNU system, type C-h C-a.
call-interactively: End of buffer [18 times]
Mark saved where search started
Mark set
Load-path shadows:
/home/rrt/.emacs.d/el-get/xrdb-mode/xrdb-mode hides /usr/share/emacs24/site-lisp/emacs-goodies-el/xrdb-mode
/home/rrt/.emacs.d/el-get/csv-mode/csv-mode hides /usr/share/emacs24/site-lisp/emacs-goodies-el/csv-mode
/home/rrt/.emacs.d/el-get/quack/quack hides /usr/share/emacs24/site-lisp/emacs-goodies-el/quack
/home/rrt/.emacs.d/el-get/markdown-mode/markdown-mode hides /usr/share/emacs24/site-lisp/emacs-goodies-el/markdown-mode
/home/rrt/.emacs.d/el-get/filladapt/filladapt hides /usr/share/emacs24/site-lisp/emacs-goodies-el/filladapt
/home/rrt/.emacs.d/el-get/graphviz-dot-mode/graphviz-dot-mode hides /usr/share/emacs24/site-lisp/emacs-goodies-el/graphviz-dot-mode
/home/rrt/.emacs.d/el-get/browse-kill-ring/browse-kill-ring hides /usr/share/emacs24/site-lisp/emacs-goodies-el/browse-kill-ring
/home/rrt/.emacs.d/el-get/apache-mode/apache-mode hides /usr/share/emacs24/site-lisp/emacs-goodies-el/apache-mode
/usr/share/emacs/24.4/site-lisp/debian-startup hides /usr/share/emacs/site-lisp/debian-startup
/home/rrt/.local/share/emacs/site-lisp/lilypond-mode hides /usr/share/emacs/site-lisp/lilypond-mode
/home/rrt/.local/share/emacs/site-lisp/lilypond-what-beat hides /usr/share/emacs/site-lisp/lilypond-what-beat
/usr/share/emacs/24.4/site-lisp/cdargs hides /usr/share/emacs/site-lisp/cdargs
/home/rrt/.emacs.d/el-get/cmake-mode/cmake-mode hides /usr/share/emacs/site-lisp/cmake-mode
/home/rrt/.local/share/emacs/site-lisp/lilypond-init hides /usr/share/emacs/site-lisp/lilypond-init
/home/rrt/.local/share/emacs/site-lisp/lilypond-song hides /usr/share/emacs/site-lisp/lilypond-song
/home/rrt/.local/share/emacs/site-lisp/lilypond-indent hides /usr/share/emacs/site-lisp/lilypond-indent
/home/rrt/.local/share/emacs/site-lisp/lilypond-font-lock hides /usr/share/emacs/site-lisp/lilypond-font-lock
/home/rrt/.local/share/emacs/site-lisp/whitespace hides /usr/share/emacs/24.4/lisp/whitespace
/usr/share/emacs24/site-lisp/dictionaries-common/ispell hides /usr/share/emacs/24.4/lisp/textmodes/ispell
/usr/share/emacs/site-lisp/rst hides /usr/share/emacs/24.4/lisp/textmodes/rst
/usr/share/emacs24/site-lisp/dictionaries-common/flyspell hides /usr/share/emacs/24.4/lisp/textmodes/flyspell
/home/rrt/.emacs.d/el-get/flymake/flymake hides /usr/share/emacs/24.4/lisp/progmodes/flymake
/home/rrt/.emacs.d/el-get/cperl-mode/cperl-mode hides /usr/share/emacs/24.4/lisp/progmodes/cperl-mode
Features:
(shadow sort mail-extr emacsbug message rfc822 mml mml-sec mm-decode
mm-bodies mm-encode mailabbrev gmm-utils mailheader sendmail mail-utils
misearch multi-isearch mule-util plain-tex gitignore-mode conf-mode
latexenc preview prv-emacs tex-buf font-latex latex tex-style tex dbus
xml crm tex-mode shell yaml-mode tern url-http tls url-auth mail-parse
rfc2231 rfc2047 rfc2045 ietf-drums url-gw json js3-mode imenu js3-parse
js3-browse js3-highlight js3-ast js3-messages js3-scan js3-util js3-vars
cc-langs js3-externs adaptive-wrap window-margin face-remap flyspell
ispell goto-addr smart-quotes org-element org-indent org-rmail org-mhe
org-irc org-info org-gnus org-docview doc-view jka-compr image-mode
org-bibtex bibtex org-bbdb org-w3m flymake compile paredit info tex-site
sws-mode-autoloads server paren savehist minibuf-eldef icomplete
autorevert filenotify desktop frameset cus-start cus-load iimage org
org-macro org-footnote org-pcomplete pcomplete org-list org-faces
org-entities noutline outline org-version ob-emacs-lisp ob ob-tangle
ob-ref ob-lob ob-table ob-exp org-src ob-keys ob-comint comint
ansi-color ob-core ob-eval org-compat org-macs org-loaddefs format-spec
find-func cal-menu calendar cal-loaddefs go-mode url url-proxy
url-privacy url-expand url-methods url-history url-cookie url-domsuf
url-util mailcap ffap thingatpt url-parse auth-source gnus-util mm-util
mail-prsvr password-cache url-vars dired-x bug-reference-github
bug-reference vc-git undo-tree diff whitespace locate yasnippet derived
po-mode php-mode etags ring cc-mode cc-fonts cc-guess cc-menus cc-cmds
cc-styles cc-align cc-engine cc-vars cc-defs speedbar sb-image ezimage
dframe init-paredit ido-hacks ido magit-autoloads geiser-load geiser
flymake-point filladapt dtrt-indent csv auto-complete-config
auto-complete edmacro kmacro popup init-eldoc eldoc-extension cl-macs
advice eldoc .loaddefs eieio byte-opt eieio-core el-get
el-get-autoloading el-get-list-packages el-get-dependencies el-get-build
el-get-status pp el-get-methods el-get-fossil el-get-svn el-get-pacman
el-get-github-zip el-get-github-tar el-get-http-zip el-get-http-tar
el-get-hg el-get-go el-get-git-svn el-get-fink el-get-emacswiki
el-get-http el-get-notify help-mode easymenu el-get-emacsmirror
el-get-github el-get-git el-get-elpa package epg-config el-get-darcs
el-get-cvs el-get-bzr el-get-brew el-get-builtin el-get-apt-get
el-get-recipes el-get-byte-compile el-get-custom el-get-core autoload
help-fns lisp-mnt bytecomp byte-compile cconv cl gv cl-loaddefs cl-lib
dired user-site-loaddefs debian-el debian-el-loaddefs emacs-goodies-el
emacs-goodies-custom emacs-goodies-loaddefs easy-mmode dpkg-dev-el
dpkg-dev-el-loaddefs devhelp time-date tooltip electric uniquify
ediff-hook vc-hooks lisp-float-type mwheel x-win x-dnd tool-bar dnd
fontset image regexp-opt fringe tabulated-list newcomment lisp-mode
prog-mode register page menu-bar rfn-eshadow timer select scroll-bar
mouse jit-lock font-lock syntax facemenu font-core frame cham georgian
utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao korean
japanese hebrew greek romanian slovak czech european ethiopic indian
cyrillic chinese case-table epa-hook jka-cmpr-hook help simple abbrev
minibuffer nadvice loaddefs button faces cus-face macroexp files
text-properties overlay sha1 md5 base64 format env code-pages mule
custom widget hashtable-print-readable backquote make-network-process
dbusbind gfilenotify dynamic-setting system-font-setting
font-render-setting move-toolbar gtk x-toolkit x multi-tty emacs)
Memory information:
((conses 16 603598 45229)
(symbols 48 50761 0)
(miscs 40 267 443)
(strings 32 171824 23445)
(string-bytes 1 5482137)
(vectors 16 49781)
(vector-slots 8 1578059 70586)
(floats 8 291 310)
(intervals 56 3314 78)
(buffers 960 28)
(heap 1024 62706 2723))
--
http://rrt.sc3d.org/
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: Workaround
2015-06-05 14:06 bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period Reuben Thomas
@ 2015-06-05 14:08 ` Reuben Thomas
2015-06-05 19:23 ` bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period Eli Zaretskii
1 sibling, 0 replies; 26+ messages in thread
From: Reuben Thomas @ 2015-06-05 14:08 UTC (permalink / raw)
To: 20741
[-- Attachment #1: Type: text/plain, Size: 102 bytes --]
As a workaround, I've added "i.e", "e.g" and "etc" to my personal word list.
--
http://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 323 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-05 14:06 bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period Reuben Thomas
2015-06-05 14:08 ` bug#20741: Workaround Reuben Thomas
@ 2015-06-05 19:23 ` Eli Zaretskii
2015-06-05 21:42 ` Reuben Thomas
1 sibling, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2015-06-05 19:23 UTC (permalink / raw)
To: Reuben Thomas; +Cc: 20741
> From: Reuben Thomas <rrt@sc3d.org>
> Date: Fri, 05 Jun 2015 15:06:40 +0100
>
> flyspell marks as incorrect “etc.”, “i.e.”, “e.g.” &c.
I can reproduce part of this with en_GB, but not with en_US. So I
think it's an issue with the dictionary, not with flyspell or ispell.
> flyspell is of course behaving as expected: “.” is in OTHERCHARS, and as
> it comes after the word, it is not included.
What OTHERCHARS are you looking at? In Emacs 24.4 and later,
ispell.el takes that value from the dictionary's .aff file, not from
the internal database. So if you customized ispell-dictionary-alist,
try without those customizations, you shouldn't need them in v24.4.
> ispell sets my default dictionary to en_GB (from my locale, I presume),
Yes. But you can override that, if you want.
> I tried to move “.” to CASECHARS and NOT-CASECHARS in a custom
> dictionary definition:
>
> ("en_GB" "[[:alpha:].]" "[^[:alpha:].]" "['0-9’-]" t
> ("-d" "en_GB")
> nil utf-8)
You shouldn't need all that in Emacs 24.4. Try not to customize the
dictionary at all.
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-05 19:23 ` bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period Eli Zaretskii
@ 2015-06-05 21:42 ` Reuben Thomas
2015-06-06 6:49 ` Eli Zaretskii
0 siblings, 1 reply; 26+ messages in thread
From: Reuben Thomas @ 2015-06-05 21:42 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 20741
[-- Attachment #1: Type: text/plain, Size: 1978 bytes --]
On 5 June 2015 at 20:23, Eli Zaretskii <eliz@gnu.org> wrote:
> > From: Reuben Thomas <rrt@sc3d.org>
> > Date: Fri, 05 Jun 2015 15:06:40 +0100
> >
> > flyspell marks as incorrect “etc.”, “i.e.”, “e.g.” &c.
>
> I can reproduce part of this with en_GB, but not with en_US. So I
> think it's an issue with the dictionary, not with flyspell or ispell.
>
The en_US dictionary contains "etc", which is incorrect.
What OTHERCHARS are you looking at? In Emacs 24.4 and later,
> ispell.el takes that value from the dictionary's .aff file, not from
> the internal database. So if you customized ispell-dictionary-alist,
> try without those customizations, you shouldn't need them in v24.4.
>
Oh dear, after further investigation this turns out to be because Debian
overrides ispell.el and flyspell.el with its own patched versions, which
predate Emacs 24.4 (they are from 2013).
In what follows, I have moved these patched files aside, and am definitely
working with just Emacs 24.4's versions!
Now, still using hunspell, and having removed "i.e", "e.g" and "etc" from
my en_GB spelling list, I get exactly the same highlighting.
> > ispell sets my default dictionary to en_GB (from my locale, I presume),
>
> Yes. But you can override that, if you want.
>
I don't want to override it, it's fine.
When I mention OTHERCHARS, I am looking at the documentation for
ispell-dictionary-alist. Indeed, when I change language, and I am using
hunspell, the language definitions seem to be auto-generated. With
hunspell, OTHERCHARS is set to include ".". But indeed, removing it or
moving it into CASECHARS and NOT-CASECHARS still seems not to help, so I'm
back to my original workaround.
But indeed, apart from when I specifically mentioned customising the
dictionary, I am working with Emacs's default values, not customised at all.
Thanks very much for your help with this.
--
http://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 3300 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-05 21:42 ` Reuben Thomas
@ 2015-06-06 6:49 ` Eli Zaretskii
2015-06-06 9:35 ` Reuben Thomas
0 siblings, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2015-06-06 6:49 UTC (permalink / raw)
To: Reuben Thomas; +Cc: 20741
> Date: Fri, 5 Jun 2015 22:42:39 +0100
> From: Reuben Thomas <rrt@sc3d.org>
> Cc: 20741@debbugs.gnu.org
>
> > flyspell marks as incorrect “etc.”, “i.e.”, “e.g.” &c.
>
> I can reproduce part of this with en_GB, but not with en_US. So I
> think it's an issue with the dictionary, not with flyspell or ispell.
>
> The en_US dictionary contains "etc", which is incorrect.
Not that I'm maintaining those dictionaries, but why is it incorrect,
in your opinion? It clearly produces the desirable effect, doesn't it?
> When I mention OTHERCHARS, I am looking at the documentation for
> ispell-dictionary-alist. Indeed, when I change language, and I am using
> hunspell, the language definitions seem to be auto-generated. With hunspell,
> OTHERCHARS is set to include ".". But indeed, removing it or moving it into
> CASECHARS and NOT-CASECHARS still seems not to help, so I'm back to my original
> workaround.
>
> But indeed, apart from when I specifically mentioned customising the
> dictionary, I am working with Emacs's default values, not customised at all.
>
> Thanks very much for your help with this.
You are welcome.
Does this mean that your problem is solved, and we can close this bug?
Or does something still need to be fixed in Emacs?
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 6:49 ` Eli Zaretskii
@ 2015-06-06 9:35 ` Reuben Thomas
2015-06-06 9:38 ` Reuben Thomas
` (2 more replies)
0 siblings, 3 replies; 26+ messages in thread
From: Reuben Thomas @ 2015-06-06 9:35 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 20741
[-- Attachment #1: Type: text/plain, Size: 1496 bytes --]
On 6 June 2015 at 07:49, Eli Zaretskii <eliz@gnu.org> wrote:
> > Date: Fri, 5 Jun 2015 22:42:39 +0100
> > From: Reuben Thomas <rrt@sc3d.org>
> > Cc: 20741@debbugs.gnu.org
> >
> > > flyspell marks as incorrect “etc.”, “i.e.”, “e.g.” &c.
> >
> > I can reproduce part of this with en_GB, but not with en_US. So I
> > think it's an issue with the dictionary, not with flyspell or ispell.
> >
> > The en_US dictionary contains "etc", which is incorrect.
>
> Not that I'm maintaining those dictionaries, but why is it incorrect,
> in your opinion? It clearly produces the desirable effect, doesn't it?
>
No, it produces the undesirable effect of treating "etc" as a correct
spelling.
Does this mean that your problem is solved, and we can close this bug?
> Or does something still need to be fixed in Emacs?
>
Sorry, I must have been unclear. I still have the original problem:
without the workaround of adding incorrect spellings to my personal
wordlist, "i.e." and "e.g." are marked as wrong in en_GB. I just double
checked this with the following recipe:
1. Rename my ~/.hunspell_en_GB.
2. Start "emacs -Q"
3. M-x flyspell-mode RET
4. M-x customize-variable RET ispell-program-name RET; set to
"/usr/bin/hunspell" (doing this after step 3 because the variable is not
available for customization before loading ispell)
5. Type "etc. i.e. e.g."
All of the above is now red-underwiggled.
--
http://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 2743 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 9:35 ` Reuben Thomas
@ 2015-06-06 9:38 ` Reuben Thomas
2015-06-06 10:03 ` Eli Zaretskii
2015-06-06 9:58 ` Eli Zaretskii
2022-02-13 9:04 ` Lars Ingebrigtsen
2 siblings, 1 reply; 26+ messages in thread
From: Reuben Thomas @ 2015-06-06 9:38 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 20741
[-- Attachment #1: Type: text/plain, Size: 413 bytes --]
On 6 June 2015 at 10:35, Reuben Thomas <rrt@sc3d.org> wrote:
>
>
> All of the above is now red-underwiggled.
>
I should add, if I M-x ispell-change-dictionary
RET american RET, then allow the underlining to refresh, "etc." is no
longer marked as wrong (as we've seen, it's incorrectly in the "american"
word list), but "i.e." and "e.g." are still so marked.
--
http://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 1135 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 9:35 ` Reuben Thomas
2015-06-06 9:38 ` Reuben Thomas
@ 2015-06-06 9:58 ` Eli Zaretskii
2022-02-13 9:04 ` Lars Ingebrigtsen
2 siblings, 0 replies; 26+ messages in thread
From: Eli Zaretskii @ 2015-06-06 9:58 UTC (permalink / raw)
To: Reuben Thomas; +Cc: 20741
> Date: Sat, 6 Jun 2015 10:35:46 +0100
> From: Reuben Thomas <rrt@sc3d.org>
> Cc: 20741@debbugs.gnu.org
>
> > The en_US dictionary contains "etc", which is incorrect.
>
> Not that I'm maintaining those dictionaries, but why is it incorrect,
> in your opinion? It clearly produces the desirable effect, doesn't it?
>
>
> No, it produces the undesirable effect of treating "etc" as a correct spelling.
I think it's a correct spelling, but that's me.
> Does this mean that your problem is solved, and we can close this bug?
> Or does something still need to be fixed in Emacs?
>
>
> Sorry, I must have been unclear. I still have the original problem: without the
> workaround of adding incorrect spellings to my personal wordlist, "i.e." and
> "e.g." are marked as wrong in en_GB. I just double checked this with the
> following recipe:
>
> 1. Rename my ~/.hunspell_en_GB.
>
> 2. Start "emacs -Q"
>
> 3. M-x flyspell-mode RET
>
> 4. M-x customize-variable RET ispell-program-name RET; set to
> "/usr/bin/hunspell" (doing this after step 3 because the variable is not
> available for customization before loading ispell)
>
> 5. Type "etc. i.e. e.g."
>
> All of the above is now red-underwiggled.
But do you agree that it's not an Emacs problem?
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 9:38 ` Reuben Thomas
@ 2015-06-06 10:03 ` Eli Zaretskii
2015-06-06 10:08 ` Reuben Thomas
0 siblings, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2015-06-06 10:03 UTC (permalink / raw)
To: Reuben Thomas; +Cc: 20741
> Date: Sat, 6 Jun 2015 10:38:17 +0100
> From: Reuben Thomas <rrt@sc3d.org>
> Cc: 20741@debbugs.gnu.org
>
> I should add, if I M-x ispell-change-dictionary
> RET american RET, then allow the underlining to refresh, "etc." is no longer
> marked as wrong (as we've seen, it's incorrectly in the "american" word list),
> but "i.e." and "e.g." are still so marked.
That's not what I see here. When en_US is used, neither of these is
flagged as a mis-spelling. When I switch to en_GB, only "etc" and the
"i" in "i.e." are flagged, the rest (including all of "e.g.") are not.
I guess the reason is the different versions of Hunspell dictionaries
we have installed.
Once again, I don't think this is an Emacs problem. Don't you see the
same when you invoke Hunspell as a stand-alone program?
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 10:03 ` Eli Zaretskii
@ 2015-06-06 10:08 ` Reuben Thomas
2015-06-06 10:11 ` Reuben Thomas
0 siblings, 1 reply; 26+ messages in thread
From: Reuben Thomas @ 2015-06-06 10:08 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 20741
[-- Attachment #1: Type: text/plain, Size: 1367 bytes --]
On 6 June 2015 at 11:03, Eli Zaretskii <eliz@gnu.org> wrote:
> > Date: Sat, 6 Jun 2015 10:38:17 +0100
> > From: Reuben Thomas <rrt@sc3d.org>
> > Cc: 20741@debbugs.gnu.org
> >
> > I should add, if I M-x ispell-change-dictionary
> > RET american RET, then allow the underlining to refresh, "etc." is no
> longer
> > marked as wrong (as we've seen, it's incorrectly in the "american" word
> list),
> > but "i.e." and "e.g." are still so marked.
>
> That's not what I see here. When en_US is used, neither of these is
> flagged as a mis-spelling. When I switch to en_GB, only "etc" and the
> "i" in "i.e." are flagged, the rest (including all of "e.g.") are not.
>
> I guess the reason is the different versions of Hunspell dictionaries
> we have installed.
>
> Once again, I don't think this is an Emacs problem. Don't you see the
> same when you invoke Hunspell as a stand-alone program?
>
$ cat Downloads/foo.txt
i.e. this is not e.g. etc. help!
foxb
$ hunspell -a -d en_GB -i UTF-8 ~/Downloads/foo.txt
@(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3)
*
*
*
*
*
*
*
& foxb 6 33: fox, fob, foxy, fox b, fixable, faux
Here we see that hunspell doesn't like "foxb" (great!) but is otherwise
happy.
So the problem does not appear to be with hunspell.
--
http://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 2284 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 10:08 ` Reuben Thomas
@ 2015-06-06 10:11 ` Reuben Thomas
2015-06-06 10:36 ` Eli Zaretskii
0 siblings, 1 reply; 26+ messages in thread
From: Reuben Thomas @ 2015-06-06 10:11 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 20741
[-- Attachment #1: Type: text/plain, Size: 693 bytes --]
On 6 June 2015 at 11:08, Reuben Thomas <rrt@sc3d.org> wrote:
>
>
> $ cat Downloads/foo.txt
> i.e. this is not e.g. etc. help!
> foxb
>
> $ hunspell -a -d en_GB -i UTF-8 ~/Downloads/foo.txt
> @(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3)
> *
> *
> *
> *
> *
> *
> *
> & foxb 6 33: fox, fob, foxy, fox b, fixable, faux
>
> Here we see that hunspell doesn't like "foxb" (great!) but is otherwise
> happy.
>
> So the problem does not appear to be with hunspell
> .
>
I should add, ispell-buffer too only complains about "foxb". It is just
flyspell that complains about the other (correct) spellings.
--
http://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 1448 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 10:11 ` Reuben Thomas
@ 2015-06-06 10:36 ` Eli Zaretskii
0 siblings, 0 replies; 26+ messages in thread
From: Eli Zaretskii @ 2015-06-06 10:36 UTC (permalink / raw)
To: Reuben Thomas; +Cc: 20741
> Date: Sat, 6 Jun 2015 11:11:22 +0100
> From: Reuben Thomas <rrt@sc3d.org>
> Cc: 20741@debbugs.gnu.org
>
> I should add, ispell-buffer too only complains about "foxb". It is just
> flyspell that complains about the other (correct) spellings.
Then I guess flyspell-word-search-forward/backward is the culprit.
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2015-06-06 9:35 ` Reuben Thomas
2015-06-06 9:38 ` Reuben Thomas
2015-06-06 9:58 ` Eli Zaretskii
@ 2022-02-13 9:04 ` Lars Ingebrigtsen
2022-02-13 12:37 ` Eli Zaretskii
2 siblings, 1 reply; 26+ messages in thread
From: Lars Ingebrigtsen @ 2022-02-13 9:04 UTC (permalink / raw)
To: Reuben Thomas; +Cc: 20741
[-- Attachment #1: Type: text/plain, Size: 537 bytes --]
Reuben Thomas <rrt@sc3d.org> writes:
> 1. Rename my ~/.hunspell_en_GB.
>
> 2. Start "emacs -Q"
>
> 3. M-x flyspell-mode RET
>
> 4. M-x customize-variable RET ispell-program-name RET; set to "/usr/bin/hunspell"
> (doing this after step 3 because the variable is not available for customization
> before loading ispell)
>
> 5. Type "etc. i.e. e.g."
>
> All of the above is now red-underwiggled.
Easier reproduction case:
----
(setq ispell-program-name "/usr/bin/hunspell")
(ispell-change-dictionary "en_GB")
(flyspell-mode)
etc.
----
[-- Attachment #2: Type: image/png, Size: 1830 bytes --]
[-- Attachment #3: Type: text/plain, Size: 57 bytes --]
Note red wiggle under etc. `M-$' on the etc gives me:
[-- Attachment #4: Type: image/png, Size: 6429 bytes --]
[-- Attachment #5: Type: text/plain, Size: 221 bytes --]
So flyspell doesn't really understand that a full stop can be part of a
word, apparently? (This is with Emacs 29.)
--
(domestic pets only, the antidote for overdose, milk.)
bloggy blog: http://lars.ingebrigtsen.no
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-13 9:04 ` Lars Ingebrigtsen
@ 2022-02-13 12:37 ` Eli Zaretskii
2022-02-13 21:33 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
0 siblings, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2022-02-13 12:37 UTC (permalink / raw)
To: Lars Ingebrigtsen; +Cc: rrt, 20741
> From: Lars Ingebrigtsen <larsi@gnus.org>
> Cc: Eli Zaretskii <eliz@gnu.org>, 20741@debbugs.gnu.org
> Date: Sun, 13 Feb 2022 10:04:38 +0100
>
> So flyspell doesn't really understand that a full stop can be part of a
> word, apparently?
Yes; and it normally isn't. Maybe we should have a list of
exceptions?
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-13 12:37 ` Eli Zaretskii
@ 2022-02-13 21:33 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 10:44 ` Lars Ingebrigtsen
2022-02-14 13:34 ` Eli Zaretskii
0 siblings, 2 replies; 26+ messages in thread
From: Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-02-13 21:33 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: Lars Ingebrigtsen, 20741
[-- Attachment #1: Type: text/plain, Size: 876 bytes --]
On Sun, 13 Feb 2022 at 12:37, Eli Zaretskii <eliz@gnu.org> wrote:
>
> Maybe we should have a list of exceptions?
>
As an upstream spellchecker maintainer, I don't think that's a good idea.
Emacs should just be using the spellchecker. If it's not working, the
problem should be fixed in the spellchecker.
As far as I can see, the problem is not specific to flyspell (mea culpa for
the bug title!).
For now, with current hunspell dictionaries, and using either hunspell, or
enchant with hunspell backend, I have used the workaround of adding a few
words like "etc" to my personal word list.
To be honest, I'm not sure Emacs can do much here. As far as I can tell,
hunspell doesn't cope well with characters like "." that normally are
non-word characters, but *can* occur in a word.
Relatedly, see: https://github.com/hunspell/hunspell/issues/361
--
https://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 2401 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-13 21:33 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-02-14 10:44 ` Lars Ingebrigtsen
2022-02-14 13:08 ` martin rudalics
2022-02-14 15:28 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 13:34 ` Eli Zaretskii
1 sibling, 2 replies; 26+ messages in thread
From: Lars Ingebrigtsen @ 2022-02-14 10:44 UTC (permalink / raw)
To: Reuben Thomas; +Cc: 20741
Reuben Thomas <rrt@sc3d.org> writes:
> To be honest, I'm not sure Emacs can do much here. As far as I can
> tell, hunspell doesn't cope well with characters like "." that
> normally are non-word characters, but *can* occur in a word.
>
> Relatedly, see: https://github.com/hunspell/hunspell/issues/361
So it's a problem on the hunspell side, and not because Emacs is
considering the "." to be a non-word character? (I haven't tried to
debug what's going on.)
There's also a problem in common abbreviations like "i.e.", which is
considered as the words "i" and "e", apparently...
I was wondering whether Emacs could query the backend speller whether it
had the word "foo." in the dictionary before squiggly-lining "foo", but
I'm very unfamiliar with how these functions work.
--
(domestic pets only, the antidote for overdose, milk.)
bloggy blog: http://lars.ingebrigtsen.no
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 10:44 ` Lars Ingebrigtsen
@ 2022-02-14 13:08 ` martin rudalics
2022-02-14 15:28 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
1 sibling, 0 replies; 26+ messages in thread
From: martin rudalics @ 2022-02-14 13:08 UTC (permalink / raw)
To: Lars Ingebrigtsen, Reuben Thomas; +Cc: 20741
> I was wondering whether Emacs could query the backend speller whether it
> had the word "foo." in the dictionary before squiggly-lining "foo", but
> I'm very unfamiliar with how these functions work.
Query and/or set WORDCHARS in the respective .aff file.
martin
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-13 21:33 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 10:44 ` Lars Ingebrigtsen
@ 2022-02-14 13:34 ` Eli Zaretskii
2022-02-14 13:43 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
1 sibling, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2022-02-14 13:34 UTC (permalink / raw)
To: Reuben Thomas; +Cc: larsi, 20741
> From: Reuben Thomas <rrt@sc3d.org>
> Date: Sun, 13 Feb 2022 21:33:32 +0000
> Cc: Lars Ingebrigtsen <larsi@gnus.org>, 20741@debbugs.gnu.org
>
> Maybe we should have a list of exceptions?
>
> As an upstream spellchecker maintainer, I don't think that's a good idea. Emacs should just be using the
> spellchecker. If it's not working, the problem should be fixed in the spellchecker.
I don't think I understand what this means in practice. "Use the
spell-checker" how? Do you mean we should not break words on
punctuation characters, or do you mean not to break them only on '.',
or do you mean something else?
Emacs is widely used to edit program sources, where stuff like
"file.attribute" and "list-my-packages" happens quite frequently.
Right now, these are not marked as misspellings, but if we pass them
to the speller with the punctuation, we are likely to get back
indications of misspelled words, which is not what we want. Thus my
questions above: if we want to handle punctuation characters smarter
than just considering them part of the NOT-CASECHARS class, we need to
come up with a specification that will improve the situation, not make
it worse. Can we do that?
> To be honest, I'm not sure Emacs can do much here.
I tend to agree, but maybe we can come up with some minor
improvements, even if they don't solve the problem in its entirety.
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 13:34 ` Eli Zaretskii
@ 2022-02-14 13:43 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 15:07 ` Stephen Berman
2022-02-14 15:21 ` Eli Zaretskii
0 siblings, 2 replies; 26+ messages in thread
From: Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-02-14 13:43 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: larsi, 20741
[-- Attachment #1: Type: text/plain, Size: 832 bytes --]
On Mon, 14 Feb 2022 at 13:35, Eli Zaretskii <eliz@gnu.org> wrote:
>
> I don't think I understand what this means in practice. "Use the
> spell-checker" how? Do you mean we should not break words on
> punctuation characters, or do you mean not to break them only on '.',
> or do you mean something else?
>
> Emacs is widely used to edit program sources, where stuff like
> "file.attribute" and "list-my-packages" happens quite frequently.
>
I wasn't considering this case, and this issue is about checking text (or
comments or strings) where you can just feed the entire thing to the
spellchecker, and not have to isolate words "manually", as in program
source.
In program source (i.e. not strings or comments), the issue currently under
discussion won't arise, as "." cannot be part of an identifier.
--
https://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 1668 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 13:43 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-02-14 15:07 ` Stephen Berman
2022-02-14 15:21 ` Eli Zaretskii
1 sibling, 0 replies; 26+ messages in thread
From: Stephen Berman @ 2022-02-14 15:07 UTC (permalink / raw)
To: 20741; +Cc: larsi, rrt
On Mon, 14 Feb 2022 13:43:12 +0000 Reuben Thomas via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@gnu.org> wrote:
> On Mon, 14 Feb 2022 at 13:35, Eli Zaretskii <eliz@gnu.org> wrote:
>
> I don't think I understand what this means in practice. "Use the
> spell-checker" how? Do you mean we should not break words on
> punctuation characters, or do you mean not to break them only on '.',
> or do you mean something else?
>
> Emacs is widely used to edit program sources, where stuff like
> "file.attribute" and "list-my-packages" happens quite frequently.
>
> I wasn't considering this case, and this issue is about checking text
> (or comments or strings) where you can just feed the entire thing to
> the spellchecker, and not have to isolate words "manually", as in
> program source.
>
> In program source (i.e. not strings or comments), the issue currently
> under discussion won't arise, as "." cannot be part of an identifier.
In some languages it can, e.g. R: "Identifiers consist of a sequence of
letters, digits, the period (‘.’) and the underscore."
(https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Identifiers)
Steve Berman
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 13:43 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 15:07 ` Stephen Berman
@ 2022-02-14 15:21 ` Eli Zaretskii
2022-02-14 15:27 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
1 sibling, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2022-02-14 15:21 UTC (permalink / raw)
To: Reuben Thomas; +Cc: larsi, 20741
> From: Reuben Thomas <rrt@sc3d.org>
> Date: Mon, 14 Feb 2022 13:43:12 +0000
> Cc: larsi@gnus.org, 20741@debbugs.gnu.org
>
> Emacs is widely used to edit program sources, where stuff like
> "file.attribute" and "list-my-packages" happens quite frequently.
>
> I wasn't considering this case, and this issue is about checking text (or comments or strings) where you can
> just feed the entire thing to the spellchecker, and not have to isolate words "manually", as in program
> source.
It's the same case: references to variables and other symbols in
comments and strings of a program are very frequent. They are also
very frequent in email messages which discuss programming, such as
this discussion (I have Flyspell turned on in all my email buffers).
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 15:21 ` Eli Zaretskii
@ 2022-02-14 15:27 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 16:42 ` Eli Zaretskii
0 siblings, 1 reply; 26+ messages in thread
From: Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-02-14 15:27 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: larsi, 20741
[-- Attachment #1: Type: text/plain, Size: 975 bytes --]
On Mon, 14 Feb 2022 at 15:21, Eli Zaretskii <eliz@gnu.org> wrote:
>
> It's the same case: references to variables and other symbols in
> comments and strings of a program are very frequent. They are also
> very frequent in email messages which discuss programming, such as
> this discussion (I have Flyspell turned on in all my email buffers).
>
I think we can distinguish 3 different problems here:
1. Natural language spellchecking. That's what this issue is about.
2. Spell-checking code. (Essentially, identifiers.)
3. Finding code inside natural language, and checking it as if it were
code. (That's what you're talking about here.) This is not a spellchecking
problem, it's a problem of identifying which spell-checking apparatus to
use, rather like font-lock for multi-language buffers. It's hard to see how
to do it without some syntactic clue (e.g. the use of backticks in
markdown), as used in multi-language buffers for font-locking.
--
https://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 1957 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 10:44 ` Lars Ingebrigtsen
2022-02-14 13:08 ` martin rudalics
@ 2022-02-14 15:28 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
1 sibling, 0 replies; 26+ messages in thread
From: Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-02-14 15:28 UTC (permalink / raw)
To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, 20741
[-- Attachment #1: Type: text/plain, Size: 1057 bytes --]
On Mon, 14 Feb 2022 at 10:45, Lars Ingebrigtsen <larsi@gnus.org> wrote:
> Reuben Thomas <rrt@sc3d.org> writes:
>
> > To be honest, I'm not sure Emacs can do much here. As far as I can
> > tell, hunspell doesn't cope well with characters like "." that
> > normally are non-word characters, but *can* occur in a word.
> >
> > Relatedly, see: https://github.com/hunspell/hunspell/issues/361
>
> So it's a problem on the hunspell side, and not because Emacs is
> considering the "." to be a non-word character? (I haven't tried to
> debug what's going on.)
For natural language, yes.
There's also a problem in common abbreviations like "i.e.", which is
> considered as the words "i" and "e", apparently...
>
This is indeed the case, and it's not normally a problem because Emacs does
not spellcheck words so short.
I was wondering whether Emacs could query the backend speller whether it
> had the word "foo." in the dictionary before squiggly-lining "foo", but
> I'm very unfamiliar with how these functions work.
>
It could!
--
https://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 2742 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 15:27 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-02-14 16:42 ` Eli Zaretskii
2022-02-14 17:01 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
0 siblings, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2022-02-14 16:42 UTC (permalink / raw)
To: Reuben Thomas; +Cc: larsi, 20741
> From: Reuben Thomas <rrt@sc3d.org>
> Date: Mon, 14 Feb 2022 15:27:54 +0000
> Cc: larsi@gnus.org, 20741@debbugs.gnu.org
>
> It's the same case: references to variables and other symbols in
> comments and strings of a program are very frequent. They are also
> very frequent in email messages which discuss programming, such as
> this discussion (I have Flyspell turned on in all my email buffers).
>
> I think we can distinguish 3 different problems here:
>
> 1. Natural language spellchecking. That's what this issue is about.
> 2. Spell-checking code. (Essentially, identifiers.)
> 3. Finding code inside natural language, and checking it as if it were code. (That's what you're talking about
> here.) This is not a spellchecking problem, it's a problem of identifying which spell-checking apparatus to
> use, rather like font-lock for multi-language buffers. It's hard to see how to do it without some syntactic clue
> (e.g. the use of backticks in markdown), as used in multi-language buffers for font-locking.
Like I said: when we talk about this stuff in email, it's both case 1
and case 3.
Anyway: what are the practical proposals for improving this? Are we
going to handle only periods, or does anyone have a more general
solution?
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 16:42 ` Eli Zaretskii
@ 2022-02-14 17:01 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 18:08 ` martin rudalics
0 siblings, 1 reply; 26+ messages in thread
From: Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2022-02-14 17:01 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: larsi, 20741
[-- Attachment #1: Type: text/plain, Size: 1593 bytes --]
On Mon, 14 Feb 2022 at 16:42, Eli Zaretskii <eliz@gnu.org> wrote:
> > From: Reuben Thomas <rrt@sc3d.org>
> >
> > I think we can distinguish 3 different problems here:
> >
> > 1. Natural language spellchecking. That's what this issue is about.
> > 2. Spell-checking code. (Essentially, identifiers.)
> > 3. Finding code inside natural language, and checking it as if it were
> code. (That's what you're talking about
> > here.) This is not a spellchecking problem, it's a problem of
> identifying which spell-checking apparatus to
> > use, rather like font-lock for multi-language buffers. It's hard to see
> how to do it without some syntactic clue
> > (e.g. the use of backticks in markdown), as used in multi-language
> buffers for font-locking.
>
> Anyway: what are the practical proposals for improving this? Are we
> going to handle only periods, or does anyone have a more general
> solution?
>
Emacs does not currently try to handle case 3, as far as I know. That would
be a medium-sized project in its own right.
Case 2 depends on per-programming-language syntax tables, not on the
spellchecker. I don't know what the current arrangements for this are.
Case 1, on which everything else depends, is a matter of Emacs sending
stretches of text to the spell-checker, which currently (at least with
Hunspell) does not deal with punctuation very well, though there are plans
to improve Hunspell, according to the issue I linked to earlier.
None of these would benefit from special-casing treatment of the period or
any other character in ispell.el, I think.
--
https://rrt.sc3d.org
[-- Attachment #2: Type: text/html, Size: 2935 bytes --]
^ permalink raw reply [flat|nested] 26+ messages in thread
* bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period
2022-02-14 17:01 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2022-02-14 18:08 ` martin rudalics
0 siblings, 0 replies; 26+ messages in thread
From: martin rudalics @ 2022-02-14 18:08 UTC (permalink / raw)
To: Reuben Thomas, Eli Zaretskii; +Cc: larsi, 20741
[-- Attachment #1: Type: text/plain, Size: 1136 bytes --]
> Case 1, on which everything else depends, is a matter of Emacs sending
> stretches of text to the spell-checker, which currently (at least with
> Hunspell) does not deal with punctuation very well, though there are plans
> to improve Hunspell, according to the issue I linked to earlier.
>
> None of these would benefit from special-casing treatment of the period or
> any other character in ispell.el, I think.
Attached find my code which works with Hunspell and utf-8 coded buffers
only. Add to your .emacs something like
(load "~/speck.el" nil t)
(custom-set-variables
'(speck-dictionaries-alist '((0 "en_US" nil nil) (1 "de_AT" nil nil) (2 "en_US" ("de_AT") nil)))
'(speck-wordchars-alist
'(("en_US" "'´`" nil)
("fr_FR" "’'" nil)
("en_US,fr_FR" "'´`’'" nil))))
(global-set-key [(f7)] 'speck-mode)
customize 'speck-wordchars-alist' according to your like (the above are
the values I use - you probably want to add options for en_GB), hit F7
in some window you want to check and tell me if special-casing treatment
of characters with that option works.
martin
[-- Attachment #2: speck.el --]
[-- Type: text/x-emacs-lisp, Size: 79761 bytes --]
;;; speck.el --- minor mode for spell checking
;; Copyright (C) 2006-2022 Martin Rudalics
;; Time-stamp: "2022-02-14 18:46:46 martin"
;; Author: Martin Rudalics <rudalics@gmx.at>
;; Keywords: spell checking
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Speck is a minor mode for "specking" - spell-checking text displayed
;; in Emacs windows. Invoke the command `speck-mode' to toggle specking
;; of all windows showing the current buffer with your machine's default
;; dictionary. Invoke `speck-buffer' to speck all windows showing the
;; current buffer choosing the dictionaries of your like.
;; In its current version Speck works exclusively with buffers coded in
;; utf-8 and Hunspell.
;; _____________________________________________________________________________
;; _
;;; Variables _
;; _____________________________________________________________________________
;; _
(defvar speck-mode nil)
(defvar speck-buffer-list nil
"List of buffers managed by Speck.")
(defvar speck-window-list nil
"List of windows managed by Speck.")
(defvar speck-delay-timer nil
"Idle timer to start specking after `speck-delay' seconds.")
(defvar speck-pause-timer nil
"Idle timer to suspend specking for `speck-pause' seconds.")
(defvar speck-marker (make-marker)
"Marker used during querying.")
(defvar speck-marker-window nil
"Window where `speck-marker' was set.")
(defvar speck-process nil
"This buffer's Speck process.")
(make-variable-buffer-local 'speck-process)
(put 'speck-process 'permanent-local t)
(defvar speck-process-marker nil
"This buffer's Speck process marker.")
(make-variable-buffer-local 'speck-process-marker)
(put 'speck-process-marker 'permanent-local t)
(defvar speck-buffer-dictionaries nil
"This buffer's Speck dictionaries.")
(make-variable-buffer-local 'speck-buffer-dictionaries)
(put 'speck-buffer-dictionaries 'permanent-local t)
(defvar speck-buffer-dictionaries-string ""
"String specifying this buffer's Speck dictionaries.")
(make-variable-buffer-local 'speck-buffer-dictionaries-string)
(put 'speck-buffer-dictionaries-string 'permanent-local t)
(defvar speck-buffer-options nil
"This buffer's list of Speck options.
This is the list of options passed to the spell engine.")
(make-variable-buffer-local 'speck-buffer-options)
(put 'speck-buffer-options 'permanent-local t)
(defvar speck-ignore-list nil
"List of words ignored in this buffer.")
(make-variable-buffer-local 'speck-ignore-list)
(put 'speck-ignore-list 'permanent-local t)
(defvar speck-log nil
"Non-nil when Speck logging is turned on")
(defvar speck-log-buffer nil)
(defun speck-log-buffer ()
"Return buffer for Speck logs."
(if (buffer-live-p speck-log-buffer)
speck-log-buffer
(get-buffer-create "*speck-log*")))
;; _____________________________________________________________________________
;; _
;;; Customization _
;; _____________________________________________________________________________
;; _
(defgroup speck nil
"Another interface to Hunspell."
:version "28.1"
:group 'applications)
(defcustom speck-delay 0.5
"Time in seconds to wait before specking.
Start specking after Emacs has been idle for that many seconds."
:type 'number
:group 'speck)
(defcustom speck-pause 0.1
"Time in seconds to pause specking.
Give other timers a chance to run while specking."
:type 'number
:group 'speck)
(defcustom speck-chunk-max 4096
"Maximum size of chunks send to spelling engine, in bytes.
This specifies the maximum number of bytes Speck may send to the
spelling engine in one step. It should not exceed the default
buffer size of the underlying operating system for sending data
over a pipe connection."
:type 'number
:group 'speck)
(defcustom speck-replace-query nil
"When non-nil query for further occurrences after correcting a word.
The commands to correct a word are `speck-popup-menu-previous',
`speck-popup-menu-next', `speck-replace-previous' and
`speck-replace-next'."
:type 'boolean
:group 'speck)
(defcustom speck-replace-preserve-point 'within
"Where to move cursor within replaced text.
Options are:
before ... before replaced text
within ... at same offset from begin of or after replaced text
after .... after replaced text"
:type '(choice (const :tag "Before" before)
(const :tag "Within" within)
(const :tag "After" after))
:group 'speck)
;; (defcustom speck-syntactic nil
;; "Non-nil means highlight misspelled words in comments or strings only.
;; Options are to highlight text anywhere in the buffer, text in
;; comments only, text in strings only, or text in comments or
;; strings.
;; The preferred way to set this option is by adding
;; (set (make-local-variable 'speck-syntactic) t)
;; to a major mode's hook."
;; :type '(choice (const :tag "Any" nil)
;; (const :tag "Comments" 'comments)
;; (const :tag "Strings" 'strings)
;; (const :tag "Comments or Strings" t))
;; :group 'speck)
(defcustom speck-hunspell-program
(locate-file "hunspell" exec-path exec-suffixes 'file-executable-p)
"File name of Hunspell program."
:type '(choice (const :tag "Invalid" nil) (file :tag "File"))
:group 'speck)
(defun speck-hunspell-executable-p ()
"Return non-nil when `speck-hunspell-program' appears executable."
(and (stringp speck-hunspell-program)
(file-executable-p speck-hunspell-program)))
(defcustom speck-hunspell-library-directory "/usr/share/hunspell/"
"Name of Hunspell library directory.
This should specify the absolute name of the directory where the
Hunspell dictionaries reside."
:type '(choice (const :tag "Invalid" nil)
(file :tag "File"))
:group 'speck)
;; The following is more useful than running Hunspell with the "-D"
;; option which may also return all sorts of myspell dictionaries.
(defun speck-hunspell-dictionaries ()
"Return list of Hunspell dictionaries installed on this machine.
This returns the relative file names sans extension of all files
with extension \".dic\" in 'speck-hunspell-library-directory'."
(when (and speck-hunspell-library-directory
(file-exists-p speck-hunspell-library-directory))
(let ((files (directory-files
speck-hunspell-library-directory nil "\\.dic$"))
dictionaries)
(dolist (file files)
(setq dictionaries
(cons (file-name-sans-extension file) dictionaries)))
(sort dictionaries 'string-lessp))))
(defvar speck-hunspell-dictionaries (speck-hunspell-dictionaries)
"List of Hunspell dictionaries installed on this machine.
If you change them, you have to reload speck.el to make speck
aware of the change.")
(defun speck-hunspell-base-dictionaries ()
"Return list of Hunspell base dictionaries installed on this machine.
This returns the relative file names sans extension of all files
with extension \".aff\" in 'speck-hunspell-library-directory'."
(when (and speck-hunspell-library-directory
(file-exists-p speck-hunspell-library-directory))
(let ((files (directory-files
speck-hunspell-library-directory nil "\\.aff$"))
dictionaries)
(dolist (file files)
(setq dictionaries
(cons (file-name-sans-extension file) dictionaries)))
(sort dictionaries 'string-lessp))))
(defvar speck-hunspell-base-dictionaries (speck-hunspell-base-dictionaries)
"List of Hunspell base dictionaries installed on this machine.
A Hunspell base dictionary is one for which an affix (.aff) file
exists in `speck-hunspell-library-directory'.
If you change them, you have to reload speck.el to make speck
aware of the change.")
;; Maybe we should check right here for UTF-8 awareness too ...
(defun speck-hunspell-default-dictionary ()
(let ((lang (getenv "LANG"))
dictionary)
(when (string-match "^\\(.*\\)\\." lang)
(setq dictionary (match-string-no-properties 1 lang))
(car (member dictionary speck-hunspell-base-dictionaries)))))
(defvar speck-hunspell-default-dictionary (speck-hunspell-default-dictionary)
"Default dictionary used by Hunspell on this machine.")
(defvar speck-dictionaries speck-hunspell-dictionaries
"List of dictionaries installed on this machine.")
(defvar speck-base-dictionaries speck-hunspell-base-dictionaries
"List of base dictionaries installed on this machine.")
(defvar speck-dictionaries-history nil
"History of dictionaries entered for `speck-buffer'.")
;; The radio button solution is not overly attractive but a simple way
;; to show all installed dictionaries to the customizer.
(defcustom speck-default-dictionary speck-hunspell-default-dictionary
"Speck's default dictionary.
The default dictionary is used for specking a buffer unless you
specify other dictionaries via `speck-dictionaries-alist' or
`speck-buffer'."
:type `(radio
:indent 2
,@(mapcar
(lambda (entry)
(list 'const :format "%v \n" entry))
speck-dictionaries))
:group 'speck)
(defcustom speck-dictionaries-alist `((0 ,speck-default-dictionary))
"List associating integers with one or more dictionaries.
This is the basic facility to specifying which dictionary to use
when specking a buffer. Speck consults the associations
specified here when determining which dictionaries to use for
specking a buffer.
The members of this list are lists of three elements.
The first element should be a non-negative integer which can be
used as numeric prefix argument for `speck-buffer' telling the
latter to use the dictionaries specified by the remainder of the
element for specking the current buffer.
The second element specifies the \"base\" dictionary for specking
a buffer when this association is used. It must be a member of
`speck-base-dictionaries'.
The third element specifies additional dictionaries that are
consulted when this association is used. They must be members of
`speck-dictionaries'. If no additional dictionaries are
specified here, only the base dictionary is used for specking.
Do not remove the association for 0 (zero). It is the default
association and when it is missing, invoking `speck-mode' (or
`speck-buffer' without a prefix argument) will break. You can,
however, change the dictionaries specified by this association.
Do not set up an association for negative values here since these
are not handled correctly."
:type `(repeat
(list :format "%v\n"
(integer :format "%v")
(choice
,@(mapcar
(lambda (entry)
(list 'const :format "%v \n" entry))
speck-base-dictionaries))
(repeat
(choice
,@(mapcar
(lambda (entry)
(list 'const :format "%v \n" entry))
speck-dictionaries)))
(choice
(const :tag "None" nil)
(string :tag "String"))))
:group 'speck)
(defvar speck-wordchars-regexp nil
"The wordchars of a speck process buffer.")
(make-variable-buffer-local 'speck-wordchars-regexp)
(put 'speck-wordchars-regexp 'permanent-local t)
(defvar speck-wordchars-function nil
"If non-nil, function used instead of `speck-wordchars-function'.")
(make-variable-buffer-local 'speck-wordchars-function)
(put 'speck-wordchars-function 'permanent-local t)
(defcustom speck-wordchars-alist nil
"Alist associating dictionary strings with word characters.
Each entry of this alist contains a list of three elements:
- a dictionary string
- a string of characters
- a function or nil.
A dictionary string is obtained by concatenating all dictionaries
specified for a buffer using a comma without spaces as separator.
If `speck-lighter' is used, Speck by default displays a buffer's
dictionary string in the buffer's mode line. See the example
below for some valid dictionary strings.
When setting up a chunk to send to the spell engine, Speck scans
that chunk provided the dictionary string specified by the first
element of this alist equals the dictionary string of the chunk's
buffer. That scan replaces within that chunk any occurrences of
a character specified by the second element of this alist not
enclosed by two characters of word syntax.
The default function to do that is `speck-wordchars' which gets
called if the third element of this alist is nil and substitutes
a space character for any such occurrence found.
If the third element is a function, that function is called with
no arguments and point immediately after the occurrence of such a
character. It is up to that function to replace that character
with exactly one other character (which must not be the newline
character) or leave that character alone. Other buffer
modifications may inevitably break specking. Note that any such
substitutions are only done on a shadow copy of the buffer text;
the original buffer is not affected by this operation.
Removing such characters is necessary if the WORDCHARS entry of a
Hunspell dictionary specifies a character that is also used in a
buffer at the beginning or end of a word. For example, when you
see problems when using the apostrophe \"'\" with the \"en_US\"
dictionary you may want to try the following cure:
- Make sure that \"'\" is specified by the WORDCHARS entry of
en_US.aff. Otherwise, Hunspell may pitiless mark \"does\" in
\"doesn't\" as misspelled.
- Make sure that an entry for en_US and \"'\" is included in this
alist. Otherwise, Hunspell will mark \"'does'\" as misspelled.
An example for customizing this option in your init file is
(customize-set-variable
\\='speck-wordchars-alist
\\='((\"en_US\" \"\\='\\=´\\=`\" nil)
(\"fr_FR\" \"\\=’\\='\" nil)
(\"en_US,fr_FR\" \"\\='\\=´\\=`\\=’\\='\" nil)))
which includes the apostrophes typically used in the specified
languages."
:type '(repeat
(list
(string :tag "Dictionary String")
(string :tag "Characters")
(choice
(const :tag "Use Speck's internal wordchars function" nil)
(function :tag "User-defined function"))))
:group 'speck)
(defcustom speck-lighter t
"When non-nil display a string in the mode-line when specking.
Compare `speck-mode-line-specking' and `speck-mode-line-specked'."
:type 'boolean
:group 'speck)
(defcustom speck-mode-line-specking t
"String displayed in mode-line while specking window.
Should contain a leading space. Selecting \"dictionary\" \(t)
here means to display this buffer's dictionaries."
:type '(choice (const :tag "Dictionary" t) string)
:group 'speck)
(defcustom speck-mode-line-specked t
"String displayed in mode-line after window has been specked.
Should contain a leading space. Selecting \"dictionary\" (t)
here means to display this buffer's dictionaries."
:type '(choice (const :tag "Dictionary" t) string)
:group 'speck)
(defface speck-guess
'((((class color)) :underline (:style wave :color "red"))
(t :underline t))
"Face for highlighting misspelled words with guesses."
:group 'speck)
(defface speck-miss
'((((class color)) :underline (:style wave :color "orange"))
(t :underline t))
"Face for highlighting misspelled words without guesses."
:group 'speck)
;; (defface speck-mouse
;; '((((class color)) :background "thistle")
;; (t :underline t))
;; "Face for highlighting misspelled word when the mouse is over it."
;; :group 'speck)
(defface speck-query
'((((class color)) :background "yellow")
(t :underline t))
"Face for highlighting word in queries."
:group 'speck-faces)
(defface speck-mode-line-specking
'((((class color)) :foreground "orange")
(t nil))
"Face for Speck lighter when window is not fully specked."
:group 'speck)
(defface speck-mode-line-specked
'((((class color)) :foreground "green")
(t nil))
"Face for Speck lighter when window is fully specked."
:group 'speck)
(defface speck-specked
'((((class color)) :background "lavenderblush1")
(t :underline t))
"Face for highlighting specked text."
:group 'speck)
(defvar speck-face-inhibit-list nil
"List of faces that inhibit specking.
If this list is not empty, a word is not marked as misspelled if
the face text property of its first character contains an element
of this list.
The recommended way to set this variable is via a major mode
hook. The following code asserts that in `emacs-lisp-mode' text
displayed with `font-lock-variable-name-face' or
`font-lock-constant-face' is not marked as misspelled.
(add-hook
\\='emacs-lisp-mode-hook
\\='(lambda ()
(set (make-local-variable \\='speck-face-inhibit-list)
\\='(font-lock-variable-name-face
font-lock-constant-face))))
This option overrides `speck-face-enforce-list' for text that has
faces in both lists.")
(defvar speck-face-enforce-list nil
"List of faces that enforce specking.
If this list is not empty, a word is not specked if the face text
property of its first character does not contain an element of
this list.
The recommended way to set this variable is via a major mode
hook. The following code asserts that in `emacs-lisp-mode' text
displayed with `font-lock-comment-face' or `font-lock-doc-face'
can be checked, while other program text remains unchecked.
(add-hook
\\='emacs-lisp-mode-hook
\\='(lambda ()
(set (make-local-variable \\='speck-face-enforce-list)
\\='(font-lock-comment-face font-lock-doc-face))
This option is overridden by `speck-face-inhibit-list' for text
that has faces in both lists.
Note that once text has been specked it will not be re-specked
unless it is modified. Face changes done by font locking as a
consequence of buffer changes, however, are usually done silently
- they do not count as buffer modifications and are not detected
by Speck. If you want to make sure that such changes are handled
by Speck, set `speck-face-enforce-contextual' to non-nil.")
(defvar speck-face-enforce-contextual nil
"Non-nil means re-speck rest of buffer after a buffer modification.
If this is nil, Speck re-scans only buffer text that actually
changed after a buffer modification. This might be insufficient
when `speck-face-enforce-list' is non-nil and Speck is supposed
to react to face changes in the remainder of a buffer after such a
modification.
If you want to make sure that any such face changes are picked up
by Speck, make this non-nil. In general, it is not recommended
to do that because it may slow down both, specking and contextual
re-fontification. In buffers where `speck-face-enforce-list' is
nil, this option has no effect.")
;; _____________________________________________________________________________
;; _
;;; Utility Functions _
;; _____________________________________________________________________________
;; _
(defun speck-string (&optional begin end)
"`buffer-substring-no-properties' with optional arguments."
(buffer-substring-no-properties
(or begin (point-min)) (or end (point-max))))
(defun speck-remove-overlays (&optional from to)
"Remove all speck overlays between FROM and TO."
(unless from (setq from (point-min)))
(unless to (setq to (setq to (point-max))))
(when (< to from)
(setq from (prog1 to (setq to from))))
(dolist (overlay (overlays-in from to))
(when (overlay-get overlay 'speck)
(delete-overlay overlay))))
(defun speck-remove-text-properties (&optional from to which)
"Remove all speck text properties between FROM and TO.
WHICH, if non-nil, specifies the text properties to remove."
(save-restriction
(widen)
(unless from (setq from (point-min)))
(unless to (setq to (setq to (point-max))))
(when (< to from)
(setq from (prog1 to (setq to from))))
(with-silent-modifications
(remove-text-properties
from to (or which '(speck nil specked nil))))))
(defun speck-add-window (&optional window)
"Add WINDOW to `speck-window-list'."
(setq window (or window (selected-window)))
(unless (memq window speck-window-list)
(with-current-buffer (window-buffer window)
(when speck-mode
(setq speck-window-list (cons window speck-window-list))
(speck-run-delay-timer)
(speck-run-pause-timer)
(force-mode-line-update)))))
(defun speck-add-buffer-windows (&optional buffer)
"Add BUFFER's windows to `speck-window-list'."
(dolist (window (get-buffer-window-list buffer nil t))
(speck-add-window window)))
(defun speck-remove-window (&optional window)
"Remove WINDOW from `speck-window-list'."
(setq window (or window (selected-window)))
(setq speck-window-list (delq window speck-window-list))
(unless speck-window-list
(when speck-delay-timer
(cancel-timer speck-delay-timer)
(setq speck-delay-timer nil))
(when speck-pause-timer
(cancel-timer speck-pause-timer)
(setq speck-pause-timer nil)))
(force-mode-line-update))
(defun speck-remove-buffer-windows (&optional buffer)
"Remove BUFFER's windows from `speck-window-list'."
(dolist (window (get-buffer-window-list buffer nil t))
(speck-remove-window window)))
(defun speck-add-buffer (&optional buffer)
"Add BUFFER to `speck-buffer-list'."
(setq buffer (or buffer (current-buffer)))
(unless (memq buffer speck-buffer-list)
(setq speck-buffer-list (cons buffer speck-buffer-list)))
(dolist (window (get-buffer-window-list buffer nil t))
(speck-add-window window)))
(defun speck-remove-buffer (&optional buffer)
"Remove BUFFER from `speck-buffer-list'."
(setq buffer (or buffer (current-buffer)))
(speck-remove-buffer-windows buffer)
(setq speck-buffer-list (remq buffer speck-buffer-list))
(with-current-buffer buffer
(when (and speck-process (process-live-p speck-process))
(kill-buffer (process-buffer speck-process)))
(setq speck-process nil)))
(defun speck-goto-marker ()
"Go to `speck-marker' and make it point nowhere."
(when (and (markerp speck-marker) (marker-position speck-marker)
(window-live-p speck-marker-window))
(select-window speck-marker-window)
(goto-char speck-marker)
(set-marker speck-marker nil)))
(defun speck-overlay-at-point (&optional at faces)
"Return speck overlay at point.
Optional argument AT non-nil means return overlay at position AT.
Optional argument FACES non-nil means return overlay if and only
if it has a face property in that list."
(setq at (or at (point)))
(let ((overlay (cdr (get-char-property-and-overlay at 'speck))))
(when (or (not faces)
(and overlay (memq (overlay-get overlay 'face) faces)))
overlay)))
(defun speck-next-overlay (&optional arg faces)
"Get first speck overlay ending after `point'.
Optional argument ARG non-nil means return ARGth overlay after
`point'. Optional argument FACES non-nil means return overlay if
and only if it has a face property in that list."
(save-excursion
(setq arg (or arg 1))
(let ((overlay (speck-overlay-at-point nil faces)))
(unless (and overlay
(or (= arg 1)
(progn
(setq arg (1- arg))
(goto-char (overlay-end overlay))
(setq overlay nil))))
(save-restriction
;; This narrowing should be safe.
(narrow-to-region (point) (window-end))
(while (and (not overlay) (< (point) (point-max)) (>= arg 0))
(goto-char (next-overlay-change (point)))
(setq overlay (speck-overlay-at-point nil faces))
(when (and overlay (> arg 1))
(setq overlay nil)
(setq arg (1- arg))))))
overlay)))
(defun speck-previous-overlay (&optional arg faces)
"Get first speck overlay starting before `point'.
Optional argument ARG non-nil means return ARGth overlay before
`point'. Optional argument FACES non-nil means return overlay if
and only if it has a face property in that list."
(save-excursion
(setq arg (or arg 1))
(let ((overlay (speck-overlay-at-point nil faces)))
(unless (and overlay
(or (< (overlay-start overlay) (point))
(setq overlay nil))
(or (= arg 1)
(progn
(setq arg (1- arg))
(goto-char (overlay-start overlay))
(setq overlay nil))))
(save-restriction
;; This narrowing should be safe.
(narrow-to-region (window-start) (point))
(while (and (not overlay) (> (point) (point-min)) (>= arg 0))
(goto-char (previous-overlay-change (point)))
(setq overlay (speck-overlay-at-point nil faces))
(when (and overlay (> arg 1))
(setq overlay nil)
(setq arg (1- arg))))))
overlay)))
(defun speck-ignored (word)
"Return non-nil if WORD is on the current buffer's list of ignored words."
(member word speck-ignore-list))
(defun speck-ignore (word)
"Add WORD to the current buffer's list of ignored words."
(setq speck-ignore-list (cons word speck-ignore-list)))
(defun speck-ignore-not (word)
"Remove WORD from the current buffer's list of ignored words.
Interactively prompt for the word to remove."
(interactive "M")
(setq speck-ignore-list (delete word speck-ignore-list))
;; We have to re-speck the entire buffer and all its windows.
(speck-remove-text-properties)
(speck-add-buffer-windows))
;; _____________________________________________________________________________
;; _
;;; Adding Words _
;; _____________________________________________________________________________
;; _
(defun speck-add-cleanup (overlay from to word)
"Clean up current buffer after WORD has been added.
OVERLAY is the overlay covering WORD, FROM and TO are
its boundaries."
(with-silent-modifications
(delete-overlay overlay)
(speck-remove-text-properties from to)
;; Remove all speck overlays and properties covering any instance
;; of WORD in this buffer.
(save-excursion
(save-restriction
(widen)
(goto-char (point-min))
(unless (get-char-property (point) 'speck)
(goto-char (or (next-single-char-property-change (point) 'speck)
(point-max)))
(let (property)
(while (not (eobp))
(setq from (point))
(setq to (or (next-single-char-property-change from 'speck)
(point-max)))
(setq overlay (cdr (get-char-property-and-overlay from 'speck)))
(when (and overlay (string-equal (speck-string from to) word))
(delete-overlay overlay)
(speck-remove-text-properties from to))
(goto-char (or (next-single-char-property-change (point) 'speck)
(point-max)))))))))
;; Add buffer's windows to `speck-window-list'.
(speck-add-buffer-windows))
(defun speck-add-word (overlay)
"Add word covered by OVERLAY to dictionary or word list."
(interactive)
(let* ((from (overlay-start overlay))
(to (overlay-end overlay))
(word (speck-string from to))
(face (overlay-get overlay 'face))
(ignored (speck-ignored word)))
(when overlay
(overlay-put overlay 'face 'speck-query)
(message (concat
(format "\"p\" adds `%s' to personal dictionary" word)
(unless (string-equal word (downcase word))
(format " (\"l\" adds `%s')" (downcase word)))
;; Suppress the following when an entry already exists.
(unless ignored (format ", \"i\" ignores it"))))
(unwind-protect
(let* ((char (read-event))
(key (vector char))
(case-fold-search t))
(cond
((and (integerp char)
(or (char-equal char ?p) (char-equal char ?*)))
(process-send-string speck-process (concat "*" word "\n"))
(process-send-string speck-process "#\n")
(speck-add-cleanup overlay from to word))
((and (integerp char)
(or (char-equal char ?l) (char-equal char ?&)))
(process-send-string speck-process (concat "&" word "\n"))
(process-send-string speck-process "#\n")
(speck-add-cleanup overlay from to word))
((and (integerp char) (char-equal char ?i) (not ignored))
(speck-ignore word)
(speck-add-cleanup overlay from to word))
(t
(setq this-command 'mode-exited)
(setq unread-command-events
(append (listify-key-sequence key)
unread-command-events)))))
;; Restore previous face.
(when (overlayp overlay)
(overlay-put overlay 'face face))))))
(defun speck-add-previous (&optional arg)
"Add previous highlighted word on selected window.
With ARG n do this for nth highlighted word preceding `point'."
(interactive "p")
(let ((overlay (speck-previous-overlay (or arg 1) '(speck-guess speck-miss))))
(if overlay
(speck-add-word overlay)
(let (message-log-max)
(message "No word found ...")))))
(defun speck-add-next (&optional arg)
"Add next highlighted word on selected window.
With ARG n do this for nth highlighted word following `point'."
(interactive "p")
(let ((overlay (speck-next-overlay (or arg 1) '(speck-guess speck-miss))))
(if overlay
(speck-add-word overlay)
(let (message-log-max)
(message "No word found ...")))))
;; _____________________________________________________________________________
;; _
;;; Menus _
;; _____________________________________________________________________________
;; _
(defun speck-menu-tail (lower ignore)
"Precalculated tail for popup menu."
(append
(list
""
(cons "---" "---")
;; Reading the correct word from the minibuffer sounds ludicrous but
;; has the advantage that one does not have to move point for
;; correcting the word in place and some history may be available.
(cons "Correct word via minibuffer" 'minibuffer)
(cons "Add to personal dictionary" 'personal))
(when lower (list (cons "Add lower-case version" 'lower)))
;; IGNORE should be non-nil here, otherwise we should never have got
;; a menu here in the first place.
(when ignore (list (cons "Ignore word in this session" 'ignore)))))
(defun speck-popup-menu (posn &optional faces)
"Pop up speck menu at position POSN."
(let ((overlay (speck-overlay-at-point nil faces))
(process speck-process))
(when (and overlay process)
;; Preempt `speck-process' and unwind-protect the following to
;; assert that preemption is canceled (we do this to avoid that
;; specking continues during popups).
(process-put process 'preempted t)
(unwind-protect
(let* ((from (overlay-start overlay))
(to (overlay-end overlay))
(word (speck-string from to))
(guesses
(let (list)
(nreverse
(dolist (item (speck-word word) list)
(setq list (cons (cons item item) list))))))
(ignored (speck-ignored word))
(speck-replace-query speck-replace-query)
(replace (x-popup-menu
posn
;; Put dictionary in menu (the user should
;; not have to guess which language is used).
(list
word
(cons "" (or guesses (list "")))
(speck-menu-tail
(not (string-equal word (downcase word)))
(not ignored))))))
(while (eq replace 'query)
(setq speck-replace-query (not speck-replace-query))
(setq replace (x-popup-menu
posn
(list
word
(cons "" (or guesses "" (list "")))
(speck-menu-tail
(not (string-equal word (downcase word)))
(not ignored))))))
(when (eq replace 'minibuffer)
(setq replace
(read-from-minibuffer
"Correct word: " word minibuffer-local-map nil
'speck-replace-history word t)))
(cond
((memq replace '(personal lower))
(if (eq replace 'personal)
(process-send-string speck-process (concat "*" word "\n"))
(process-send-string
speck-process (concat "&" word "\n")))
(process-send-string speck-process "#\n")
(speck-add-cleanup overlay from to word))
((and (eq replace 'ignore) (not ignored))
(speck-ignore word)
(speck-add-cleanup overlay from to word))
(replace
(unless (atom replace)
(setq replace (car replace)))
(speck-replace-word from to word replace overlay))))
(process-put process 'preempted nil)))))
(defun speck-popup-menu-at-point (&optional at point)
"Pop up speck menu.
Optional arguments AT and POINT if set mean popup menu at position AT
and return to position POINT afterwards. At least one letter of the
incorrect word must appear at the right of `point'."
(interactive)
(set-marker speck-marker (or point (point)))
(setq speck-marker-window (selected-window))
(when at (goto-char at))
(let ((posn (posn-at-point)))
;; Always jump back to `speck-marker'.
(unwind-protect
(speck-popup-menu
(list (list (car (posn-x-y posn)) (cdr (posn-x-y posn)))
(posn-window posn)))
(speck-goto-marker))))
(defun speck-popup-menu-previous (&optional arg)
"Popup menu for previous word with guesses or miss.
With ARG n do this for nth such word preceding `point'."
(interactive "p")
(let ((overlay (speck-previous-overlay (or arg 1) '(speck-guess speck-miss))))
(if overlay
(speck-popup-menu-at-point (overlay-start overlay) (point))
(let (message-log-max)
(message "No word found ...")))))
(defun speck-popup-menu-next (&optional arg)
"Popup menu for next word with guesses or miss.
With ARG n do this for nth such word following `point'."
(interactive "p")
(let ((overlay (speck-next-overlay (or arg 1) '(speck-guess speck-miss))))
(if overlay
(speck-popup-menu-at-point (overlay-start overlay) (point))
(let (message-log-max)
(message "No word found ...")))))
(defun speck-mouse-popup-menu (event)
"Pop up speck menu at mouse-position.
Should be bound to a click event."
(interactive "e")
(set-marker speck-marker (window-point) (current-buffer))
(setq speck-marker-window (selected-window))
(mouse-set-point event)
;; Always jump back to `speck-marker'.
(unwind-protect
(speck-popup-menu event)
(speck-goto-marker)))
;; _____________________________________________________________________________
;; _
;;; Parsing _
;; _____________________________________________________________________________
;; _
(defun speck-make-overlay (from to face)
"Make overlay from FROM to TO with face FACE."
(unless (or (speck-ignored (speck-string from to))
(and (eq major-mode 'texinfo-mode)
(or (eq (char-before from) ?\@)
(save-excursion
(goto-char from)
(beginning-of-line)
(looking-at "\\@\\(?:\\(?:def\\(?:un\\|var\\|opt\\|fn\\)\\)\\|end\\)")))))
(let ((overlay (make-overlay from to)))
(overlay-put overlay 'speck t)
(overlay-put overlay 'evaporate t)
(overlay-put overlay 'face face)
(overlay-put overlay 'keymap speck-overlay-map))))
(defun speck-wordchars (wordchars-regexp)
"Speck's internal wordchars function."
(goto-char (point-min))
;; Replace char at BOB.
(when (looking-at wordchars-regexp)
(replace-match " ")
(forward-char))
(while (re-search-forward wordchars-regexp nil t)
(backward-char 1)
(if (eq (char-syntax (char-before)) ?\w)
(progn
(forward-char)
(when (or (eobp) (not (eq (char-syntax (char-after)) ?\w)))
;; Replace char at EOB or followed by a non-word-char.
(delete-char -1)
(insert ?\ )))
;; Replace char preceded by a non-word-char.
(delete-char 1)
(insert ?\ )
(forward-char))))
(defun speck-log (&rest rest)
"Log REST in our Log buffer."
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(when rest
(insert (format "%s" (car rest)))
(setq rest (cdr rest))
(while rest
(insert (format " %s" (car rest)))
(setq rest (cdr rest)))
(insert "\n"))))
(defun speck-chunk (from-1 to-1 process window-buffer from to)
"Speck current buffer's chunk from FROM-1 to TO-1.
The current buffer contains the stretch to parse when this gets
called and FROM-1 and TO-1 are the start and end of the chunk to
send to the spelling process PROCESS .
WINDOW-BUFFER is the buffer where the chunk originally stems from
and FROM and TO are the start and end positions of the stretch
that contains this chunk. Any overlays representing guesses and
misses are made in WINDOW-BUFFER by adding the chunk start
position FROM-1 to the stretch start position FROM."
(let (done pos at length word log-chunk log-output read)
;; Process stale output.
(with-current-buffer (process-buffer process)
(sit-for 0.05)
(erase-buffer)
(move-marker speck-process-marker (point-min)))
(save-excursion
;; Insert a leading "^" and a final newline, send the region to
;; the spell process and revert the insertions immediately.
(goto-char to-1) (insert-char ?\n)
(goto-char from-1) (insert "^")
(process-send-region process from-1 (+ to-1 2))
(goto-char from-1) (delete-char 1)
(goto-char to-1) (delete-char 1))
(with-current-buffer (process-buffer process)
(sit-for 0.1)
(when speck-log
(let ((size (- (point-max) (point-min)))
(string (speck-string (point-min) (min (point-max) 20))))
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "FIRST: %s (%s-%s) %s\n" size from to string)))))
;; Quit if there is input.
(while (and (not done)
(or (and (not quit-flag ) (not (input-pending-p)))
(progn
(with-current-buffer window-buffer
(when (process-live-p speck-process)
(let ((process-buffer (process-buffer speck-process)))
(when (buffer-live-p process-buffer)
(kill-buffer process-buffer))))
(setq speck-process nil)
(speck-re-start-process
speck-buffer-dictionaries-string speck-buffer-options))
(when speck-log
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert "RESTART\n")))
(setq done nil)))
(progn
(sit-for 0.1)
(when speck-log
(let ((size (- (point-max) (point-min)))
(string (speck-string 1 (min (point-max) 20))))
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "READ: %s (%s-%s) %s\n" size from to string)))))
(or (> (point-max) (point-min))
(progn
(when speck-log
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert "NO OUTPUT\n")))
(setq done nil)))))
(when speck-log
(setq log-output (speck-string)))
(goto-char speck-process-marker)
;; This is still a hard loop we do not interrupt yet. But note
;; that it has all ingredients with the buffer position of each
;; misspelled item - so we could quit there, if we want to.
(while (re-search-forward "\\(^& \\)\\|\\(^# \\)" nil 'noerror)
(cond
((match-beginning 1) ; &
(setq at (point))
(setq length (skip-chars-forward "^ "))
(setq word (speck-string at (point)))
(skip-chars-forward " ")
(skip-chars-forward "0-9")
(skip-chars-forward " ")
(setq at (point))
(skip-chars-forward "^:")
(setq pos (string-to-number (speck-string at (point))))
(when (= (forward-line) 0)
(move-marker speck-process-marker (point))
(with-current-buffer window-buffer
(let ((from-2 (+ from pos -1))
(to-2 (+ from pos length -1)))
(speck-make-overlay from-2 to-2 'speck-guess)
(when speck-log
(let ((string (speck-string from-2 to-2)))
(cond
((not (string-equal word string))
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "BAD WORD: %s at: %s is not: %s\n"
word from-2 string))))
((and (< to-2 (point-max))
(eq (char-syntax (char-after to-2)) ?\w))
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "INCOMPLETE: %s at: %s is not: %s\n"
word from-2 string)))))))))))
((match-beginning 2) ; #
(setq at (point))
(setq length (skip-chars-forward "^ "))
(setq word (speck-string at (point)))
(skip-chars-forward " ")
(setq at (point))
(skip-chars-forward "0-9")
(setq pos (string-to-number (speck-string at (point))))
(when (= (forward-line) 0)
(move-marker speck-process-marker (point))
(with-current-buffer window-buffer
(let ((from-2 (+ from pos -1))
(to-2 (+ from pos length -1)))
(speck-make-overlay from-2 to-2 'speck-guess)
(when speck-log
(let ((string (speck-string from-2 to-2)))
(cond
((not (string-equal word string))
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "BAD WORD: %s at: %s is not: %s\n"
word from-2 string))))
((and (< to-2 (point-max))
(eq (char-syntax (char-after to-2)) ?\w))
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "INCOMPLETE: %s at: %s is not: %s\n"
word from-2 string)))))))))))))
;; With errors we get two newlines at EOB, without errors a
;; buffer containing one newline character only. Set done when
;; the spelling engine has sent us everything for this chunk.
(when (and (eq (char-before (point-max)) ?\n)
(or (= (point-min) (1- (point-max)))
(eq (char-before (1- (point-max))) ?\n)))
(setq done t)))
(when speck-log
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "CHUNK DONE: %s (%s-%s)\n" done from to))))
done)))
(defun speck-stretch (from to)
"Speck current buffer's stretch from FROM to TO."
(let ((done t)
(process speck-process)
(window-buffer (current-buffer))
(wordchars-function speck-wordchars-function)
(wordchars-regexp speck-wordchars-regexp)
from-1 to-1 log-stretch)
(with-current-buffer (get-buffer-create "*speck-stretch*")
(setq buffer-undo-list t)
(erase-buffer)
;; A real `insert-buffer-substring-no-properties' is a pipe dream.
(insert-buffer-substring-no-properties window-buffer from to)
;; Replace wordchars with spaces. It would be tempting to check
;; these when making the overlays but then we would have to send
;; the word proper a second time because the word might be
;; misspelled or it might be enclosed by the wordchars, or both!
;; Sending the word a second time would mean to interrupt the
;; underlying chunk logic so we do that replacement here.
(when wordchars-function
(goto-char (point-min))
(funcall wordchars-function wordchars-regexp))
;; Replace newlines with spaces. We probably should do that for
;; chunks only but it's too tempting to run it in one rush here.
(goto-char (point-min))
(while (re-search-forward "\n" nil 'noerror)
(replace-match " "))
(when speck-log
;; Log stretch - its first and last lines, at least.
(goto-char (point-min))
(if (and (re-search-forward "\n" nil 'noerror) (not (eobp)))
(let ((point (point)))
(setq log-stretch (speck-string nil (1- (point))))
(goto-char (point-max))
(skip-chars-backward "[ \n\t]")
(if (> (point) point)
(setq log-stretch
(concat log-stretch "..."
(speck-string
(line-beginning-position) (point))))
(setq log-stretch (speck-string nil (point)))))
(setq log-stretch (speck-string)))
(with-current-buffer (speck-log-buffer)
(insert (format "STRETCH (%s-%s): %s\n" from to log-stretch))))
;; Extract chunks from stretch.
(goto-char (point-min))
(setq from-1 (point-min))
(while (and done (< from-1 (point-max)))
;; Set to-1 to the largest buffer position preceding a
;; 'speck-chunk-max' byte offset after from-1.
(let ((to-1 (or (byte-to-position
(+ (position-bytes from-1) speck-chunk-max))
(point-max))))
(when (< to-1 (point-max))
(goto-char to-1)
(skip-chars-backward "^ \t")
(setq to-1 (point)))
(if (<= to-1 from-1)
;; If searching for a whitespace has got us before from-1,
;; we have a chunk that does not fit, that is, an overly
;; long non-whitespace string. Just mark it as specked.
(progn
(skip-chars-forward "^ \t")
(setq to-1 (point))
(with-current-buffer window-buffer
(let ((from-2 (+ from from-1 -1))
(to-2 (+ from to-1 -1)))
(with-silent-modifications
(put-text-property from-2 to-2 'specked t)
(when speck-log
(with-current-buffer (speck-log-buffer)
(insert (format "IGNORE: %s-%s\n" from-2 to-2)))
(put-text-property
from-2 to-2 'face 'speck-specked)))))
(setq to-1 (point)))
;; A chunk that fits, process it.
(setq done
(speck-chunk
from-1 to-1 process window-buffer
(+ from from-1 -1) (+ from to-1 -1)))
(when done
(let ((from-2 (+ from from-1 -1))
(to-2 (+ from to-1 -1)))
(when speck-log
(with-current-buffer (speck-log-buffer)
(insert (format "SPECKED %s-%s\n" from-2 to-2))))
(with-current-buffer window-buffer
(with-silent-modifications
(put-text-property from-2 to-2 'specked t)
(when speck-log
(put-text-property
from-2 to-2 'face 'speck-specked)))))))
(setq from-1 to-1)))
(when speck-log
(with-current-buffer (speck-log-buffer)
(goto-char (point-max))
(insert (format "STRETCH DONE: %s (%s-%s)\n" done from to))))
done)))
;; (setq speck-face-enforce-list '(font-lock-doc-face font-lock-comment-face))
(defun speck-enforce-face-at-point ()
"Return non-nil when a face property at point is enforced."
(unless (eobp)
(let ((faces (get-text-property (point) 'face)))
(cond
((not faces)
nil)
((listp faces)
;; We have a list of face properties.
(catch 'found
(dolist (face faces t)
(when (memq face speck-face-enforce-list)
(throw 'found t)))))
(t ; Atom.
(memq faces speck-face-enforce-list))))))
(defun speck-window (&optional window)
"Speck specified WINDOW."
(let* ((window (or window (selected-window)))
(window-buffer (window-buffer window))
(window-start (window-start window))
(window-end (window-end window))
(from window-start)
(done t)
to from-1)
(with-current-buffer window-buffer
(when (and speck-process (not (process-get speck-process 'preempted)))
(with-silent-modifications
(let (minibuffer-auto-raise message-log-max)
(save-excursion
(when speck-face-enforce-list
(save-excursion
(goto-char window-start)
;; from-1 non-nil points at the first position that
;; should not be specked.
(setq from-1 (unless (speck-enforce-face-at-point)
(point)))
(while (and (< (point) window-end)
(goto-char (next-single-property-change
(point) 'face nil window-end)))
(cond
((or (= (point) window-end)
(speck-enforce-face-at-point))
(when from-1
(with-silent-modifications
;; Mark text from from-1 to point as specked.
(put-text-property from-1 (point) 'specked t)
(when speck-log
(put-text-property
from-1 (point) 'face 'speck-specked))))
(setq from-1 nil))
(t
;; Expand from previous from-1 or start new
;; stretch that should not be specked.
(setq from-1 (or from-1 (point))))))))
(while (and (or (and (not quit-flag) (not (input-pending-p)))
(setq done nil))
(setq from (text-property-any from window-end 'specked nil)))
(setq to (next-single-property-change from 'specked nil window-end))
(when speck-log
(speck-log "WINDOW:" window "buffer:" window-buffer
"start:" window-start "end:" window-end
"from:" from "to:" to))
(unless (speck-stretch from to)
(setq done nil))
(setq from to)))))))
(when done
(speck-remove-window window))))
(defun speck-respeck (delay)
"Speck again after DELAY seconds."
(timer-set-idle-time speck-pause-timer (current-idle-time))
(timer-inc-time speck-pause-timer (or delay 0))
(timer-activate-when-idle speck-pause-timer t))
(defun speck-windows (&optional pause)
"Speck windows on `speck-window-list'.
Works correctly if and only if the optional argument PAUSE is nil
when triggered by `speck-delay-timer' and non-nil when triggered
by `speck-pause-timer'."
(unless pause
;; When `pause' is nil cancel `speck-pause-timer' (in pathological
;; cases this might interfere with the current call).
(cancel-timer speck-pause-timer))
(cond
((or (input-pending-p)
;; (active-minibuffer-window) ; do we need this?
executing-kbd-macro defining-kbd-macro)
;; Pause by `speck-delay' seconds (maybe the list above should be
;; extended).
(speck-respeck speck-delay))
;; Test selected window first.
((and (memq (selected-window) speck-window-list)
(or (let ((buffer (window-buffer)))
(and (local-variable-p 'speck-mode buffer)
(buffer-local-value 'speck-mode buffer)))
;; The selected window is not suitable for specking, remove
;; it from `speck-window-list' (could it ever get there?).
(and (speck-remove-window (selected-window))
nil)))
(speck-window))
(t
(let* (windows-to-remove
(window
(catch 'found
;; Scan `speck-window-list'
(dolist (window speck-window-list)
(if (and (window-live-p window)
(let ((buffer (window-buffer window)))
(and (local-variable-p 'speck-mode buffer)
(buffer-local-value 'speck-mode buffer))))
;; `window' is suitable for specking, return it.
(throw 'found window)
;; `window' is not suitable for specking, remove it
;; from `speck-window-list'. FIXME, this is hairy ...
(setq windows-to-remove (cons window windows-to-remove)))))))
;; Remove dead windows.
(while windows-to-remove
(speck-remove-window (car windows-to-remove))
(setq windows-to-remove (cdr windows-to-remove)))
;; Speck `window'.
(speck-window window))))
(when speck-window-list
;; Pause by `speck-pause' seconds.
(speck-respeck speck-pause)))
(defun speck-after-change (start end old-len)
"Speck after a text change.
START, END, and OLD-LEN have the usual meanings."
(when speck-mode
(save-excursion
(save-restriction
(widen)
(goto-char start)
(skip-chars-backward "^ \n\t")
(skip-chars-forward " \n\t")
(setq start (point))
(goto-char end)
(skip-chars-forward "^ \n\t")
(skip-chars-backward " \n\t")
(setq end (point))
(with-silent-modifications
(if (and speck-face-enforce-list
speck-face-enforce-contextual)
(progn
(speck-remove-overlays start (point-max))
(speck-remove-text-properties start (point-max)))
(speck-remove-overlays start end)
(speck-remove-text-properties start end)))))
(speck-add-buffer-windows)))
(defun speck-window-scroll (window _start)
"Speck after WINDOW was scrolled."
(speck-add-window window))
(defun speck-window-state-change (window)
"Speck after WINDOW changed state."
(speck-add-window window))
;; _____________________________________________________________________________
;; _
;;; Keymaps _
;; _____________________________________________________________________________
;; _
(defvar speck-overlay-map
(let ((map (make-sparse-keymap)))
(define-key map [down-mouse-3] 'speck-mouse-popup-menu)
map)
"Speck mouse map.")
(defun speck-make-mode-map (map)
"Assign `speck-mode-keys' to MAP which should be `speck-mode-map'."
(when (boundp 'speck-mode-keys)
(define-key map (nth 0 speck-mode-keys) 'speck-popup-menu-previous)
(define-key map (nth 1 speck-mode-keys) 'speck-popup-menu-next)
(define-key map (nth 2 speck-mode-keys) 'speck-replace-previous)
(define-key map (nth 3 speck-mode-keys) 'speck-replace-next)
(define-key map (nth 4 speck-mode-keys) 'speck-add-previous)
(define-key map (nth 5 speck-mode-keys) 'speck-add-next)))
(defvar speck-mode-map
(let ((map (make-sparse-keymap)))
(speck-make-mode-map map)
map)
"Keymap used by `speck-mode'. `speck-make-mode-map' fills it.")
(defcustom speck-mode-keys
'([(control ?\.)] [(control meta ?\.)]
[(control ?\,)] [(control meta ?\,)]
[(control ?\+)] [(control meta ?\+)]
[(control ?\!)] [(control meta ?\!)]
[(control ?\?)] [(control meta ?\?)])
"Keys used by `speck-mode'."
:type
'(list
(key-sequence
:tag "Popup menu at previous word" :format "\n %t %v\n\n"
:value '[(control ?\.)] :size 20)
(key-sequence
:tag "Popup menu at next word " :format " %t %v\n\n"
:value '[(control meta ?\.)] :size 20)
(key-sequence
:tag "Replace previous word " :format " %t %v\n\n"
:value '[(control ?\,)] :size 20)
(key-sequence
:tag "Replace next word " :format " %t %v\n\n"
:value '[(control meta ?\,)] :size 20)
(key-sequence
:tag "Accept previous word " :format " %t %v\n\n"
:value '[(control ?\+)] :size 20)
(key-sequence
:tag "Accept next word " :format " %t %v\n\n"
:value '[(control meta ?\+)] :size 20)
(key-sequence
:tag "Spell-check region " :format " %t %v\n\n"
:value '[(control ?\!)] :size 20)
(key-sequence
:tag "Change dictionary " :format " %t %v\n\n"
:value '[(control meta ?\!)] :size 20)
(key-sequence
:tag "Set language " :format " %t %v\n\n"
:value '[(control ?\?)] :size 20)
(key-sequence
:tag "Set option " :format " %t %v\n\n"
:value '[(control meta ?\?)] :size 20))
:set #'(lambda (symbol value)
(when (and (boundp 'speck-mode-map)
;; Paranoia.
(boundp 'speck-mode-keys)
(listp speck-mode-keys))
(dolist (key speck-mode-keys)
(define-key speck-mode-map key nil)))
(set-default symbol value)
(when (boundp 'speck-mode-map)
(speck-make-mode-map speck-mode-map)))
:group 'speck)
(defun speck-assign-keys-to-map (map keys)
"Assign KEYS to MAP.
MAP must be a keymap, KEYS a list of (command . key) pairs."
(dolist (pair keys)
(define-key map (cdr pair) (car pair))))
(defcustom speck-replace-keys
'((help . [(control ?\?)])
(help . [(control ?\h)])
(help . [f1])
(help . [help])
(accept . [(control ?\!)])
(accept-and-quit . [(control ?\.)])
(reject-and-quit . [(control ?\-)])
(reject-and-quit . [(control ?\g)])
(reject-and-quit . [(control ?\])])
(reject-and-quit . [escape])
(forward . [(control ?\,)])
(backward . [(control meta ?\,)]))
"Keys used by `speck-mode' during replacement."
:type
'(repeat
(cons :format "%v"
(choice :format " %[Command%] %v"
(const :format "help " help)
(const :format "accept " accept)
(const :format "accept-and-quit" accept-and-quit)
(const :format "reject-and-quit" reject-and-quit)
(const :format "forward " forward)
(const :format "backward " backward))
(key-sequence :format " Key: %v\n\n" :size 20)))
:set #'(lambda (symbol value)
;; Don't "and" these.
(when (boundp 'speck-replace-map)
(when (boundp 'speck-replace-keys)
(dolist (pair speck-replace-keys)
;; Reset them all.
(define-key speck-replace-map (cdr pair) nil))))
(set-default symbol value)
(when (boundp 'speck-replace-map)
(speck-assign-keys-to-map speck-replace-map speck-replace-keys)))
:group 'speck)
(defvar speck-replace-map
(let ((map (make-sparse-keymap)))
(speck-assign-keys-to-map map speck-replace-keys)
map)
"Dummy keymap for `speck-replace'.")
(defcustom speck-replace-query-keys
'((help . [(control ?\?)])
(help . [(control ?\h)])
(help . [f1])
(help . [help])
(accept . [(control ?\!)])
(accept . [?\ ])
(accept . [return])
(accept-and-quit . [(control ?\.)])
(reject . [(control ?\-)])
(reject-and-quit . [(control ?\g)])
(reject-and-quit . [(control ?\])])
(reject-and-quit . [escape])
(forward . [(control ?\,)])
(forward . [tab])
(backward . [(control meta ?\,)])
(backward . [(shift tab)]))
"Keys used by `speck-mode' during query replacement."
:type
'(repeat
(cons :format "%v"
(choice :format " %[Command%] %v"
(const :format "help " help)
(const :format "accept " accept)
(const :format "accept-and-quit" accept-and-quit)
(const :format "reject " reject)
(const :format "reject-and-quit" reject-and-quit)
(const :format "forward " forward)
(const :format "backward " backward))
(key-sequence :format " Key: %v\n\n" :size 20)))
:set #'(lambda (symbol value)
;; Don't "and" these.
(when (boundp 'speck-replace-query-map)
(when (boundp 'speck-replace-query-keys)
(dolist (pair speck-replace-query-keys)
;; Reset them all.
(define-key speck-replace-query-map (cdr pair) nil))))
(set-default symbol value)
(when (boundp 'speck-replace-query-map)
(speck-assign-keys-to-map
speck-replace-query-map speck-replace-query-keys)))
:group 'speck)
(defvar speck-replace-query-map
(let ((map (make-sparse-keymap)))
(speck-assign-keys-to-map map speck-replace-query-keys)
map)
"Dummy keymap for `speck-replace-query'.")
(defun speck-key-help (command keys suffix)
"Return string of keys in KEYS executing COMMAND.
KEYS must be either `speck-replace-keys' or
`speck-replace-query-keys'."
(let ((string ""))
(dolist (key keys)
(when (eq command (car key))
(setq string
(concat
string
(unless (string-equal string "") ", ") ; Looks better.
(key-description (cdr key))))))
(if (string-equal string "")
""
(concat " " string suffix)))) ; Prefix this with two spaces.
(defun speck-keys-help (keys &optional first)
"Return a readable list of keybindings for help."
(concat
;; Use a fixed list of commands here, it's simpler. Yes we do
;; allocate string space here, but after all this should be used only
;; sporadically.
(speck-key-help 'accept keys
(concat " to accept the replacement and "
(if first "query further occurrences" "continue querying") "\n"))
(speck-key-help 'accept-and-quit keys
" to accept the replacement and quit querying\n")
(speck-key-help 'reject keys
" to reject the replacement and continue querying\n")
(speck-key-help 'reject-and-quit keys
" to reject the replacement and quit querying\n")
(speck-key-help 'forward keys " to display the next replacement\n")
(speck-key-help 'backward keys " to display the previous replacement\n")
(speck-key-help 'help keys " to display this help\n")))
;; _____________________________________________________________________________
;; _
;;; Replacing words _
;; _____________________________________________________________________________
;; _
(defun speck-replace-word (from to word replace &optional overlay)
"Replace WORD within FROM and TO by REPLACE.
Optional OVERLAY non-nil means remove that overlay. PROPERTY non-nil
means put this property on REPLACE."
(let (move-to)
(when overlay
(delete-overlay overlay))
(when (and (eq (marker-buffer speck-marker)
(current-buffer))
(<= from speck-marker)
(<= speck-marker to))
(cond
((eq speck-replace-preserve-point 'before)
(setq move-to from))
((and (eq speck-replace-preserve-point 'within)
(<= from speck-marker)
(<= speck-marker to)
(< (- speck-marker from)
(length replace)))
(setq move-to (marker-position speck-marker)))
(t (setq move-to (+ from (length replace))))))
(delete-region from to)
(goto-char from)
(insert replace)
(when move-to
(set-marker speck-marker move-to))
;; The following never worked here. Maybe I misunderstand this
;; completely.
;; (speck-send-replacement word replace)
))
(defun speck-replace-put-overlay (overlay from to offset replace)
"Put OVERLAY and goto FROM or TO."
(if offset
(cond
((eq speck-replace-preserve-point 'before)
(overlay-put overlay 'display replace)
(goto-char from))
((and (eq speck-replace-preserve-point 'within)
(< offset (length replace)))
(overlay-put
overlay 'display
(concat (substring replace 0 offset)
(propertize
(substring replace offset (1+ offset)) 'cursor t)
(substring replace (1+ offset))))
(goto-char from))
(t
(overlay-put overlay 'display replace)
(goto-char to)))
(overlay-put overlay 'display replace)))
(defun speck-replace-query (word replace)
"Query replace further occurrences of WORD by something like REPLACE."
(let ((regexp (concat "\\<" (regexp-quote word) "\\>"))
(query t)
(case-fold-search t)
(text
(substitute-command-keys
"Replace `%s' with `%s'? Type \\<speck-replace-query-map>\\[help] for help.")))
;; Consider widening here.
;; Consider using `undo-boundary' here.
(goto-char (point-min))
(while (and query (not (eobp))
(re-search-forward regexp nil t))
(let* ((from (match-beginning 0))
(to (match-end 0))
(word (speck-string from to))
(begin (line-beginning-position))
(end (line-beginning-position 2))
guesses tail)
(when (and (not (and query-replace-skip-read-only
;; Ignore matches with read-only property.
(text-property-not-all
(match-beginning 0) (match-end 0)
'read-only nil)))
(save-excursion
(and (goto-char from)
;; When we enforce face, make sure to not
;; operate outside of such a face.
(or (not speck-face-enforce-list)
(speck-enforce-face-at-point))
;; occur.
(consp (setq guesses (speck-word word))))))
(when (setq tail (member-ignore-case replace guesses))
;; REPLACE is in `guesses'.
(unless (eq guesses tail)
;; Move REPLACE to head of list.
(setq guesses
(cons (car tail)
(delete (car tail) guesses)))))
(let* ((replace (car guesses))
(reps-vector (vconcat guesses))
(reps-index 0)
(reps-max (1- (length reps-vector)))
(overlay (or (speck-overlay-at-point
from '(speck-guess speck-miss))
(make-overlay from to)))
(def 'forward)
change key)
(overlay-put overlay 'speck t)
(overlay-put overlay 'display replace)
(overlay-put overlay 'face 'speck-query)
(unwind-protect
(while (memq def '(forward backward help))
(setq query nil)
(setq def nil)
(let ((message-log-max nil))
;; This message is needed to avoid echoing typed
;; characters in the echo area (see replace.el).
(message text word replace))
(setq key (vector (read-event)))
(setq def (lookup-key speck-replace-query-map key))
(cond
((eq def 'accept)
(setq change t)
(setq query t))
((eq def 'accept-and-quit)
(setq change t))
((eq def 'reject)
(setq query t))
((eq def 'reject-and-quit))
((eq def 'forward)
(setq reps-index
(if (= reps-index reps-max) 0 (1+ reps-index)))
(setq replace (aref reps-vector reps-index))
(overlay-put overlay 'display replace))
((eq def 'backward)
(setq reps-index
(if (zerop reps-index) reps-max (1- reps-index)))
(setq replace (aref reps-vector reps-index))
(overlay-put overlay 'display replace))
((eq def 'help)
(with-output-to-temp-buffer "*Help*"
(princ
(concat
"Replace `" word "' with `" replace "'? Type\n\n"
(speck-keys-help speck-replace-query-keys)
"\nAnything else will accept the replacement and reread as command.\n"))
(with-current-buffer standard-output
(help-mode))))
(t
;; The mode-exited stuff is not clean but let's try
;; doing this as in `query-replace'.
(setq this-command 'mode-exited)
(setq unread-command-events
(append (listify-key-sequence key)
unread-command-events))
(setq change t))))
(cond
(change
(speck-replace-word from to word replace overlay))
((overlayp overlay)
;; Install or restore overlay properties.
(overlay-put overlay 'display nil) ; Silly
(overlay-put overlay 'face 'speck-guess)))
(unless query (speck-goto-marker)))))))))
(defun speck-replace (overlay)
"Replace word covered by OVERLAY with corrections."
(let ((process speck-process))
(when (and overlay process)
(process-put process 'preempted t)
(unwind-protect
(let* ((from (overlay-start overlay))
(to (overlay-end overlay))
(offset (when (and (< from (point))
(< (point) to))
;; Offset of `point' wrt `from'.
(- (point) from)))
(word (speck-string from to))
(guesses (speck-word word))
(text
;; We can't use any "`" or "'" here, these characters
;; may be part of the word or the replacement. Hence
;; entirely rely on faces (`speck-query') to set them
;; apart from the rest.
(substitute-command-keys
(concat
"Replace %s with %s ? Type \\<speck-replace-map>\\[help] for help."))))
(if (null guesses)
(message "No corrections found")
(let* ((replace (car guesses))
(guess-vector (vconcat guesses))
(guess-index 0)
(guess-max (1- (length guess-vector)))
(def 'forward)
change query key)
(set-marker speck-marker (point))
(setq speck-marker-window (selected-window)) ; <----
(speck-replace-put-overlay overlay from to offset replace)
(overlay-put overlay 'face 'speck-query)
(unwind-protect
(progn
(while (memq def '(forward backward help))
(let ((message-log-max nil))
;; This message is also needed to avoid
;; echoing typed characters in the echo area
;; (see replace.el).
(message
text (propertize word 'face 'speck-query)
(propertize replace 'face 'speck-query)))
(setq key (vector (read-event)))
(setq def (lookup-key speck-replace-map key))
(cond
((eq def 'accept)
(setq change t)
(setq query t))
((eq def 'accept-and-quit)
(setq change t))
((memq def '(reject reject-and-quit)))
((eq def 'forward)
(setq guess-index
(if (= guess-index guess-max) 0 (1+ guess-index)))
(setq replace (aref guess-vector guess-index))
(speck-replace-put-overlay overlay from to offset replace))
((eq def 'backward)
(setq guess-index
(if (zerop guess-index) guess-max (1- guess-index)))
(setq replace (aref guess-vector guess-index))
(speck-replace-put-overlay overlay from to offset replace))
((eq def 'help)
(with-output-to-temp-buffer "*Help*"
(princ
(concat
"Replace `" word "' with `" replace "'? Type\n\n"
(speck-keys-help speck-replace-keys t)
"\nAnything else will accept the replacement and reread as command.\n"))
(with-current-buffer standard-output
(help-mode))))
(t
;; The mode-exited stuff is not clean but
;; let's try doing this as in `query-replace'.
(setq this-command 'mode-exited)
(setq unread-command-events
(append (listify-key-sequence key)
unread-command-events))
(setq change t)))))
(cond
(change
(speck-replace-word from to word replace overlay))
((overlayp overlay)
;; Restore overlay properties.
(overlay-put overlay 'display nil) ; Silly
(overlay-put overlay 'face 'speck-guess)))
(when (and query speck-replace-query)
(speck-replace-query (downcase word) replace))
(speck-goto-marker)))))
(process-put process 'preempted nil)))))
(defun speck-replace-previous (&optional arg)
"Correct previous word with guesses in place.
With ARG n do this for nth such word preceding `point'."
(interactive "p")
(let ((overlay (speck-previous-overlay (or arg 1) '(speck-guess))))
(if overlay
(speck-replace overlay)
(let (message-log-max)
(message "No previous word found ...")))))
(defun speck-replace-next (&optional arg)
"Correct next word with guesses in place.
With ARG n do this for nth such word following `point'."
(interactive "p")
(let ((overlay (speck-next-overlay (or arg 1) '(speck-guess))))
(if overlay
(speck-replace overlay)
(let (message-log-max)
(message "No next word found ...")))))
;; _____________________________________________________________________________
;; _
;;; Syntactic _
;; _____________________________________________________________________________
;; _
;; (defalias 'speck-jitify 'jit-lock-fontify-now)
;; (defalias 'speck-lazify 'lazy-lock-fontify-region)
;; ;; Stefan's idea of doing this.
;; (defsubst speck-ensure-fontified (start end)
;; (cond
;; ((and (boundp 'jit-lock-mode) (symbol-value 'jit-lock-mode))
;; (speck-jitify start end))
;; ((and (boundp 'lazy-lock-mode) (symbol-value 'lazy-lock-mode))
;; (speck-lazify start end))))
;; (defun speck-syntactic-p ()
;; "Return t when character at `point' may be syntactically checked."
;; (and (or (not speck-syntactic)
;; (let ((parse-state (syntax-ppss)))
;; (or (and (nth 3 parse-state)
;; (memq speck-syntactic '(strings t)))
;; (and (nth 4 parse-state)
;; (memq speck-syntactic '(comments t))))))
;; (or (not speck-face-inhibit-list)
;; (progn
;; (unless (get-text-property (point) 'fontified)
;; (speck-ensure-fontified
;; (line-beginning-position) (line-end-position))
;; nil))
;; (let ((faces (get-text-property (point) 'face)))
;; ;; Inhibit specking this word if (one of) its face(s) is in
;; ;; `speck-face-inhibit-list'.
;; (cond
;; ((not faces))
;; ((listp faces)
;; ;; We have a list of face properties.
;; (catch 'found
;; (dolist (face faces t)
;; (when (memq face speck-face-inhibit-list)
;; (throw 'found nil)))))
;; (t ; Atom.
;; (not (memq faces speck-face-inhibit-list))))))))
;; _____________________________________________________________________________
;; _
;;; Process management _
;; _____________________________________________________________________________
;; _
(defun speck-filter (process string)
"Speck process filter function"
(when (buffer-live-p (process-buffer process))
(with-current-buffer (process-buffer process)
(save-excursion
(goto-char (point-max))
(insert string)
;; No use for it but ...
(set-marker (process-mark process) (point))))))
(defun speck-re-start-process (dictionaries-string options)
"Return old or new process for current buffer."
(or (and (process-live-p speck-process)
(or (and (string-equal
speck-buffer-dictionaries-string dictionaries-string)
(equal speck-buffer-options options)
speck-process)
(let ((process-buffer (process-buffer speck-process)))
;; Dictionaries or options don't match, kill old
;; process.
(delete-process speck-process)
(when (buffer-live-p process-buffer)
(kill-buffer process-buffer))
(setq speck-process nil))))
(let ((process
(make-process
:name "speck"
:buffer (generate-new-buffer "*speck*")
:command (append (list "hunspell" "-a" "-d" dictionaries-string)
options)
:connection-type 'pipe
:filter #'speck-filter
:noquery t)))
(with-current-buffer (process-buffer process)
(setq speck-process-marker (make-marker))
(setq buffer-undo-list t))
;; Always turn on terse mode - note the newline!
(process-send-string process "!\n")
(setq speck-process process)
(setq speck-buffer-dictionaries-string dictionaries-string)
process)))
(defun speck-start-process (dictionaries &optional options)
"Start Speck process for current buffer."
(let ((dictionaries-string (car dictionaries))
process)
(setq dictionaries (cdr dictionaries))
(while dictionaries
(setq dictionaries-string
;; No space after the comma.
(concat dictionaries-string "," (car dictionaries)))
(setq dictionaries (cdr dictionaries)))
(speck-re-start-process dictionaries-string options)))
(defun speck-word (word)
"Send a word-like object to `speck-process' and return list of guesses."
(let* (guesses process)
(unless speck-process
;; THIS SHOULD NOT HAVE HAPPENED
(speck-start-process speck-buffer-dictionaries speck-buffer-options))
(setq process speck-process)
(with-current-buffer (process-buffer process)
(erase-buffer)
(process-send-string process (concat "^" word "\n"))
(sit-for 0.05)
(goto-char (point-min))
(when (re-search-forward ": " nil t)
(while (re-search-forward "\\(.*?\\)\\(?:, \\|\n\n\\)" nil t)
(setq guesses (cons (match-string-no-properties 1) guesses)))
(when guesses (nreverse guesses))))))
(defun speck-run-delay-timer ()
"Run `speck-delay-timer'.
This is an idle timer called each time Emacs has been idle for
`speck-delay' seconds."
(unless speck-delay-timer
(setq speck-delay-timer
(run-with-idle-timer speck-delay t 'speck-windows))))
(defun speck-run-pause-timer ()
"Run `speck-pause-timer'.
This is an idle timer called after Emacs has been idle for
`speck-pause' seconds. It's activated by `speck-windows'."
(unless speck-pause-timer
(setq speck-pause-timer (timer-create))
(timer-set-function speck-pause-timer 'speck-windows '(t))))
(defun speck-activate ()
"Activate specking for current buffer."
(unless speck-buffer-dictionaries
(setq speck-buffer-dictionaries
(or (let* ((entry (assoc 0 speck-dictionaries-alist))
(base (nth 1 entry))
(others (nth 2 entry)))
(cons base others))
(list speck-default-dictionary))))
;; Make this customizable somehow since it overrides a personal
;; dictionary specified via `speck-buffer-options'.
(when (and (eq major-mode 'texinfo-mode) buffer-file-name)
(let ((spellfile
(concat
(file-name-directory buffer-file-name) "spellfile")))
(when (file-exists-p spellfile)
(setq speck-buffer-options
(append speck-buffer-options (list "-p" spellfile))))))
(speck-remove-overlays)
(speck-remove-text-properties)
(add-hook 'after-change-functions 'speck-after-change nil t)
(add-hook 'kill-buffer-hook 'speck-remove-buffer nil t)
(add-hook 'window-scroll-functions 'speck-window-scroll nil t)
(add-hook 'window-state-change-functions 'speck-window-state-change nil t)
(speck-start-process speck-buffer-dictionaries speck-buffer-options)
;; Set up wordchars function (to change it, you have to restart speck
;; in this buffer).
(dolist (entry speck-wordchars-alist)
(when (string-equal (nth 0 entry) speck-buffer-dictionaries-string)
(setq speck-wordchars-regexp (concat "[" (nth 1 entry) "]"))
(setq speck-wordchars-function
(if (functionp (nth 2 entry))
(nth 2 entry)
'speck-wordchars))))
(speck-add-buffer)
(speck-run-delay-timer)
(speck-run-pause-timer))
(defun speck-deactivate ()
"Deactivate specking for current buffer."
(setq speck-mode nil)
;; Intentionally do not reset the following. It is used when
;; reactivating Speck in this buffer via `speck-mode'.
;; (setq speck-buffer-dictionaries nil)
;; Remove text properties and overlays.
(speck-remove-text-properties)
(speck-remove-overlays)
(speck-remove-buffer)
(remove-hook 'kill-buffer-hook 'speck-remove-buffer t)
(remove-hook 'after-change-functions 'speck-after-change t)
(remove-hook 'window-scroll-functions 'speck-window-scroll t))
;;;###autoload
(defun speck-buffer (&optional arg)
"Toggle `speck-mode' selecting a dictionary.
With ARG nil or omitted use the dictionary specifed by
`speck-default-dictionary'. With a numeric prefix argument ARG
use the corresponding entry from `speck-dictionaries-alist'.
Otherwise prompt for a dictionary."
(interactive "P")
;; (require 'speck)
(let (dictionaries options)
(cond
((not arg)
(setq dictionaries (list speck-default-dictionary)))
((numberp arg)
(let ((entry (assoc arg speck-dictionaries-alist)))
(if entry
(progn
(setq dictionaries (cons (nth 1 entry) (nth 2 entry)))
(setq options (nth 3 entry)))
(message "No association found for \"%s\"" arg))))
(t
(let ((dictionary
(completing-read
(concat
"Enter " (when speck-mode "new ")
"dictionary (RET for default, SPC to complete): ")
;; (mapcar 'list (cons "default" speck-base-dictionaries))
speck-base-dictionaries nil t nil
speck-dictionaries-history speck-default-dictionary)))
(setq dictionaries (list dictionary)))))
(when (and (equal dictionaries speck-buffer-dictionaries)
(equal options speck-buffer-options))
(message "Buffer dictionaries and options unchanged"))
(when speck-mode
(speck-deactivate)
;; Hunspell occasionally hangs when restarting, maybe the
;; following helps.
(sit-for 0.1))
(setq speck-buffer-dictionaries dictionaries)
(setq speck-buffer-options options)
(speck-mode)))
(defun speck-lighter ()
"Speck lighter."
(propertize
(if (stringp speck-mode-line-specking)
speck-mode-line-specking
(concat " " speck-buffer-dictionaries-string))
'face (if (memq (selected-window) speck-window-list)
'speck-mode-line-specking
'speck-mode-line-specked)))
;;;###autoload
(define-minor-mode speck-mode
"Toggle `speck-mode'.
With prefix ARG, turn speck-mode on if and only if ARG is
positive. Turning on speck-mode will spell-check (\"speck\") all
windows showing the current buffer.
Global bindings (customizable via `speck-mode-keys').
\\{speck-mode-map}"
:group 'speck
:init-value nil
:lighter (:eval (when speck-lighter (speck-lighter)))
:keymap speck-mode-map
:require 'speck
(if speck-mode
(speck-activate)
(speck-deactivate)))
(provide 'speck)
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2022-02-14 18:08 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-06-05 14:06 bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period Reuben Thomas
2015-06-05 14:08 ` bug#20741: Workaround Reuben Thomas
2015-06-05 19:23 ` bug#20741: 24.4; flyspell doesn't work with abbreviations ending in a period Eli Zaretskii
2015-06-05 21:42 ` Reuben Thomas
2015-06-06 6:49 ` Eli Zaretskii
2015-06-06 9:35 ` Reuben Thomas
2015-06-06 9:38 ` Reuben Thomas
2015-06-06 10:03 ` Eli Zaretskii
2015-06-06 10:08 ` Reuben Thomas
2015-06-06 10:11 ` Reuben Thomas
2015-06-06 10:36 ` Eli Zaretskii
2015-06-06 9:58 ` Eli Zaretskii
2022-02-13 9:04 ` Lars Ingebrigtsen
2022-02-13 12:37 ` Eli Zaretskii
2022-02-13 21:33 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 10:44 ` Lars Ingebrigtsen
2022-02-14 13:08 ` martin rudalics
2022-02-14 15:28 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 13:34 ` Eli Zaretskii
2022-02-14 13:43 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 15:07 ` Stephen Berman
2022-02-14 15:21 ` Eli Zaretskii
2022-02-14 15:27 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 16:42 ` Eli Zaretskii
2022-02-14 17:01 ` Reuben Thomas via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-02-14 18:08 ` martin rudalics
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).