-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathwcheck-mode.el
2225 lines (1817 loc) · 85.1 KB
/
wcheck-mode.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; wcheck-mode.el --- General interface for text checkers -*- lexical-binding: t -*-
;; Copyright (C) 2009-2021 Free Software Foundation, Inc.
;; Author: Teemu Likonen <tlikonen@iki.fi>
;; Maintainer: Teemu Likonen <tlikonen@iki.fi>
;; Created: 2009-07-04
;; URL: https://github.com/tlikonen/wcheck-mode
;; Keywords: text spell check languages ispell
;; Version: 2021
;; 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.
;;
;; The license text: <http://www.gnu.org/licenses/gpl-3.0.html>
;; INSTALLATION
;;
;; Put this file to some directory in your Emacs's "load-path" and add
;; the following lines to Emacs's initialization file (~/.emacs):
;;
;; (autoload 'wcheck-mode "wcheck-mode"
;; "Toggle wcheck-mode." t)
;; (autoload 'wcheck-change-language "wcheck-mode"
;; "Switch wcheck-mode languages." t)
;; (autoload 'wcheck-actions "wcheck-mode"
;; "Open actions menu." t)
;; (autoload 'wcheck-jump-forward "wcheck-mode"
;; "Move point forward to next marked text area." t)
;; (autoload 'wcheck-jump-backward "wcheck-mode"
;; "Move point backward to previous marked text area." t)
;;
;; See customize group "wcheck" for information on how to configure
;; Wcheck mode. (M-x customize-group RET wcheck RET)
;;; Commentary:
;;
;; A general interface for text checkers
;;
;; Wcheck mode is a general-purpose text-checker interface for Emacs
;; text editor. Wcheck mode a minor mode which provides an on-the-fly
;; text checker. It checks the visible text area, as you type, and
;; possibly highlights some parts of it. What is checked and how are all
;; configurable.
;;
;; Wcheck mode can use external programs or Emacs Lisp functions for
;; checking text. For example, Wcheck mode can be used with
;; spell-checker programs such as Ispell, Enchant and Hunspell, but
;; actually any tool that can receive text from standard input stream
;; and send text to standard output can be used. Wcheck mode sends parts
;; of buffer's content to an external program or an Emacs Lisp function
;; and, relying on their output, decides if some parts of text should be
;; marked in the buffer.
;;; Code:
(eval-when-compile
;; Silence compiler
(declare-function outline-show-entry "outline"))
;;; Settings
;;;###autoload
(defgroup wcheck nil
"General interface for text checkers."
:group 'applications)
(defconst wcheck--language-data-customize-interface
'(choice
:format "%[Option%] %v"
(cons :tag "Program" :format "%v"
(const :tag "Program" :format "%t: " program)
(choice :format "%[Type%] %v"
(file :tag "Filename" :format "\n\t\t%t: %v")
(function :tag "Function" :format "\n\t\t%t: %v")))
(cons :tag "Arguments" :format "%v"
(const :format "" args)
(repeat :tag "Arguments"
(string :format "%v")))
(cons :tag "Output parser function" :format "%v"
(const :tag "Output parser" :format "%t: " parser)
(choice :format "%[Parser%] %v" :value nil
(const :tag "Lines" wcheck-parser-lines)
(const :tag "Whitespace" wcheck-parser-whitespace)
(function :tag "Custom function"
:format "%t:\n\t\t%v")))
(cons :tag "Connection type" :format "%v"
(const :tag "Connection: " :format "%t" connection)
(choice :format "%[Type%] %v" :value nil
(const :tag "pipe (nil)" nil)
(const :tag "pty" :match (lambda (widget value)
(or (eq value t)
(eq value 'pty)))
pty)))
(cons :tag "Face" :format "%v"
(const :tag "Face" :format "%t: " face)
(symbol :format "%v"))
(cons :tag "Syntax table" :format "%v"
(const :tag "Syntax table" :format "%t: " syntax)
(variable :format "%v"))
(cons :tag "Regexp start" :format "%v"
(const :tag "Regexp start" :format "%t: " regexp-start)
(regexp :format "%v"))
(cons :tag "Regexp body" :format "%v"
(const :tag "Regexp body" :format "%t: " regexp-body)
(regexp :format "%v"))
(cons :tag "Regexp end" :format "%v"
(const :tag "Regexp end" :format "%t: " regexp-end)
(regexp :format "%v"))
(cons :tag "Regexp discard" :format "%v"
(const :tag "Regexp discard" :format "%t: " regexp-discard)
(regexp :format "%v"))
(cons :tag "Regexp case" :format "%v"
(const :tag "Regexp" :format "%t: " case-fold)
(choice :format "%[Case%] %v" :value nil
:match (lambda (widget value) t)
:value-to-internal (lambda (widget value)
(if value t nil))
(const :tag "sensitive" nil)
(const :tag "insensitive" t)))
(cons
:tag "Read or skip faces" :format "%v"
(const :tag "Read or skip faces" :format "%t" read-or-skip-faces)
(repeat
:tag ""
(cons :format "%v"
(choice :format "%[Major mode%] %v"
(const :tag "All major modes"
:match (lambda (widget value) (null value))
nil)
(repeat
:tag "Select major modes"
:match (lambda (widget value)
(or (symbolp value) (consp value)))
:value-to-internal (lambda (widget value)
(if (symbolp value)
(list value)
value))
:value-to-external (lambda (widget value)
(if (and (consp value)
(symbolp (car value))
(null (cdr value)))
(car value)
value))
(symbol :format "%v")))
(choice :format "%[Operation mode%] %v"
(const :tag "Read everything" nil)
(cons :tag "Read selected faces" :format "%v"
(const :tag "Read selected faces"
:format "%t" read)
(repeat :tag "" (sexp :format "%v")))
(cons :tag "Skip selected faces" :format "%v"
(const :tag "Skip selected faces"
:format "%t" skip)
(repeat :tag "" (sexp :format "%v")))))))
(cons :tag "Action program" :format "%v"
(const :tag "Action program" :format "%t: " action-program)
(choice :format "%[Type%] %v"
(file :tag "Filename" :format "\n\t\t%t: %v")
(function :tag "Function" :format "\n\t\t%t: %v")))
(cons :tag "Action program's arguments" :format "%v"
(const :format "" action-args)
(repeat :tag "Action program's arguments"
(string :format "%v")))
(cons :tag "Action parser function" :format "%v"
(const :tag "Action parser" :format "%t: "
action-parser)
(choice :format "%[Parser%] %v" :value nil
(const :tag "Ispell" wcheck-parser-ispell-suggestions)
(const :tag "Lines" wcheck-parser-lines)
(const :tag "Whitespace" wcheck-parser-whitespace)
(function :tag "Custom function"
:format "%t:\n\t\t%v")))
(cons :tag "Action autoselect mode" :format "%v"
(const :tag "Action autoselect" :format "%t: " action-autoselect)
(choice :format "%[Mode%] %v" :value nil
:match (lambda (widget value) t)
:value-to-internal (lambda (widget value)
(if value t nil))
(const :tag "off" nil)
(const :tag "on" t)))))
;;;###autoload
(defcustom wcheck-language-data
;; FIXME: Auto-fill by looking at installed spell-checkers and dictionaries!
nil
"Language configuration for `wcheck-mode'.
The variable is an association list (alist) and its elements are
of the form:
(LANGUAGE (KEY . VALUE) [(KEY . VALUE) ...])
LANGUAGE is a name string for this particular configuration unit
and KEY and VALUE pairs denote settings for the language.
Below is the documentation of possible KEYs and corresponding
VALUEs. The documentation is divided in two parts: checker
options and action options. The first part describes all options
related to checking the content of an Emacs buffer (and possibly
marking some of it). The second part describes options which
configure actions which user can choose for a marked text on
buffer.
NOTE: There is also variable `wcheck-language-data-defaults'
which is used to define default values. The defaults are used
when a language-specific option in `wcheck-language-data' does
not exist or is not valid.
Checker options
---------------
The checker options configure LANGUAGE's text-checking and
text-marking features. With these you can configure how buffer's
content is examined, what checker engine is used and how text is
marked in the buffer.
program
args
`program' is either the name (a string) of an external
executable program or an Emacs Lisp function (a symbol or a
lambda expression). They are used as the checker engine for
the LANGUAGE. When `program' names an external executable
program then `args' are the command-line arguments (a list of
strings) for the program.
`wcheck-mode' collects text strings from the buffer and sends
them to `program' to analyze. When `program' is an external
executable program the collected strings are sent (each on a
separate line) through the standard input stream to the
program. The program must write to standard output stream all
the strings which it thinks should be marked in the Emacs
buffer. The output of the program is then parsed with
`parser' function (see below).
When `program' is an Emacs Lisp function (a symbol or a
lambda expression) the function is called with one argument:
a list of strings collected from the buffer. The function is
supposed to check them and return a list of strings (or nil).
The returned strings will be marked in the buffer.
See options `regexp-start', `regexp-body' and `regexp-end'
below for details on how text is collected from the buffer.
parser
VALUE of this option is an Emacs Lisp function which is
responsible for parsing the output of `program'. This parser
function is only used when `program' is an external
executable program (not a function).
The parser function is run without arguments and within the
context of a buffer that contains all the output from the
external program. The point is located at the beginning of
the buffer. From that buffer the `parser' function should
collect all the strings that are meant to be marked in the
buffer that is being checked. The function must return them
as a list of strings or nil if there are none to be marked.
For the most common cases there are two parser functions
already implemented:
`wcheck-parser-lines' turns each line in program's output
to a separate string. You should use this function as the
output parser if you spell-check with Ispell-like program
with its \"-l\" command-line option. They output each
misspelled word on a separate line. This is the default
output parser.
`wcheck-parser-whitespace' turns each whitespace-
separated token in the output to a separate string.
connection
The VALUE is used to set variable `process-connection-type'
when starting the process for LANGUAGE. If the VALUE is nil
use a pipe for communication; if it's `pty' (or t) use a PTY.
The default is to use a pipe (nil). (This option is ignored
when the program is a function.)
face
A symbol referring to the face which is used to mark text with
this LANGUAGE. The default is `wcheck-default-face'.
syntax
VALUE is a variable (a symbol) referring to an Emacs syntax
table. This option temporarily sets the effective syntax
table when buffer's content is scanned with `regexp-start',
`regexp-body', `regexp-end' and `regexp-discard' (see below)
as well as when `program', `parser', `action-program' and
`action-parser' functions are called. The default value is
`text-mode-syntax-table'. This option does not affect syntax
table settings anywhere else. See the Info node
`(elisp)Syntax Tables' for more information on the topic.
regexp-start
regexp-body
regexp-end
Regular expression strings which match the start of a string
body, characters within the body and the end of the body,
respectively.
This is how they are used in practice: `wcheck-mode' scans
buffer's content and looks for strings that match the
following regular expression
REGEXP-START\\(REGEXP-BODY\\)REGEXP-END
The regular expression back reference \\1 is used to extract
`regexp-body' part from the matched string. That string is
then matched against `regexp-discard' (see below) and if it
doesn't match the string is sent to the text checker program
or function to analyze.
Strings returned from the program or function are quoted for
regular expression special characters (with `regexp-quote'
function) and marked in Emacs buffer using the following
construction: `regexp-start + STRING + regexp-end'. The
STRING part is marked with `face' (see above).
You can't use grouping constructs `\\( ... \\)' in
`regexp-start' because the back reference `\\1' is used for
separating the `regexp-body' match string from the
`regexp-start' and `regexp-end' match strings. You can use
\"shy\" groups `\\(?: ... \\)' which do not record the
matched substring. Grouping constructs `\\( ... \\)' are
allowed in `regexp-body' and `regexp-end'. Just note that the
first group and back reference \\1 is already taken.
The default values for the regular expressions are
\\=\\<\\='* (regexp-start)
\\w+? (regexp-body)
\\='*\\=\\> (regexp-end)
Effectively they match a series of word characters defined in
the effective syntax table. Single quotes (\\=') at the start
and end of a word are excluded. This is probably a good thing
when using `wcheck-mode' as a spelling checker.
regexp-discard
The string that matched `regexp-body' is then matched against
the value of this option. If this regular expression matches,
then the string is discarded and won't be sent to the
text-checker program or function to analyze. You can use this
to define exceptions to the `regexp-body' match. The default
value is
\\\\=`\\='+\\\\='
which discards the body string if it consists only of single
quotes. This was chosen as the default because the default
syntax table `text-mode-syntax-table' defines single quote as
a word character. It's probably not useful to mark individual
single quotes in a buffer when `wcheck-mode' is used as a
spelling checker.
If you don't want to have any discarding rules set this
option to empty string (\"\").
case-fold
This boolean value is used to temporarily bind the value of
variable `case-fold-search'. The nil value means
case-sensitive and a non-nil means case-insensitive search.
The default is case-sensitive (nil). This option is effective
with `regexp-start', `regexp-body', `regexp-end' and
`regexp-discard' as well as when `program', `parser',
`action-program' and `action-parser' functions are called.
read-or-skip-faces
This option controls which faces `wcheck-mode' should read or
skip when scanning buffer's content. The value must be a list
and its items are also lists:
(MAJOR-MODE [OPERATION-MODE [FACE ...]])
MAJOR-MODE is a symbol or a list of symbols. Symbols refer to
the major mode(s) which the settings are for. Use nil as the
MAJOR-MODE to define default settings. Settings that come
after the pseudo major-mode nil are ignored.
OPERATION-MODE is symbol `read' or `skip' defining whether
the FACEs should be read or skipped. If it's `read' then only
the listed faces are read. If it's `skip' then the listed
faces are skipped and all other faces are read. If there is
no OPERATION-MODE at all (i.e., the list has just one
element, MAJOR-MODE) then everything is read.
The rest of the items are FACEs. They are typically symbols
but some Emacs modes may use strings, property lists or cons
cells for defining faces. For more information see Info
node `(elisp) Special Properties'. Use nil as the face to
refer to the normal text which does not have a face text
property.
Example:
(read-or-skip-faces
((emacs-lisp-mode c-mode) read
font-lock-comment-face font-lock-doc-face)
(org-mode skip font-lock-comment-face org-link)
(text-mode)
(nil read nil))
It says that in `emacs-lisp-mode' and `c-mode' only the text
which have been highlighted with `font-lock-comment-face' or
`font-lock-doc-face' is read (i.e., checked). In `org-mode'
faces `font-lock-comment-face' and `org-link' are
skipped (i.e., not checked) and all other faces are read. In
`text-mode' everything is read. Finally, in all other major
modes only the normal text (nil) is read.
Most likely not all `read-or-skip-faces' settings are
specific to a certain language so it could be more useful to
put them in variable `wcheck-language-data-defaults' instead.
That way they are used with all languages. Normally the
global default is equivalent to
(read-or-skip-faces
(nil))
which means that in all major modes read everything. It is
sometimes useful to have this setting in language-specific
options because the parsing stops right there. Therefore it
overrides all global settings which user may have changed
with variable `wcheck-language-data-defaults'.
Note: You can use command `\\[what-cursor-position]' with a
prefix argument to see what faces are active at the cursor
position. Then you can use the information to configure this
option.
Action options
--------------
\"Actions\" are any kind of operations that can be executed for
marked text in an Emacs buffer. Actions are presented to user
through a menu which is activated either by (1) clicking the
right mouse button on a marked text or (2) executing interactive
command `wcheck-actions' while the cursor (the point) is on a
marked text.
If you use `wcheck-mode' as a spelling checker then it's natural
to configure an action menu that offers spelling suggestions for
the misspelled word. The action menu could also have an option to
add the word to spell-checker's dictionary, so that the word is
recognized next time.
action-program
action-args
`action-program' is either the name (a string) of an external
executable program or an Emacs Lisp function (a symbol or a
lambda expression). When it's the name of an executable
program then `action-args' are the command-line arguments (a
list of strings) for the program.
When `action-program' is an external executable program the
marked text is sent to the program through the standard input
stream. The program should send its feedback data (usually
suggested substitute strings) to the standard output stream.
The output is parsed with `action-parser' function (see
below) and function's return value is used to construct an
action menu for user. The format and effect of
`action-parser' function's return value is described below.
When `action-program' is an Emacs Lisp function the function
is called with one argument: a vector returned by
`wcheck-marked-text-at' function. The `action-program'
function is supposed to gather some substitute suggestion
strings or give other actions for the marked text in the
buffer. Function's return value is used to construct an
action menu for user. The format and effect of
`action-program' function's return value is described below.
action-parser
VALUE of this option is an Emacs Lisp function which is
responsible for parsing the output of `action-program'. This
parser function is only used when `action-program' is an
external executable program (not a function).
The parser function is run with one argument: a vector
returned by `wcheck-marked-text-at' for the marked text in
question. The parser function is called within the context of
a buffer that contains all the output from `action-program'.
The point is located at the beginning of the buffer.
The `action-parser' function should examine the buffer for
interesting information (such as spelling suggestions) and
return them in the format that is described below.
For the most common cases there are three parser functions
already implemented:
`wcheck-parser-ispell-suggestions' parses substitute
suggestions from the output of Ispell or compatible
program, such as Enchant. Use this function as the
`action-parser' if you get spelling suggestions from an
Ispell-like program with its \"-a\" command-line option.
`wcheck-parser-lines' function turns each line in the
output to individual substitute suggestions.
`wcheck-parser-whitespace'. Each whitespace-separated
token in the program's output is a separate suggestion.
action-autoselect
If this option is non-nil and the action menu has only one
menu item then the item is chosen automatically without
actually showing the menu. If this option is nil (which is
the default) then the menu is always shown.
The return value of `action-program' function and `action-parser'
function must be a list. The empty list (nil) means that there
are no actions available for the marked text. Otherwise each
elements in the list must be either a string or a cons cell. If
an element is a string it is an individual substitute suggestion
string for the original marked text. The same string is shown in
the actions menu. When user chooses such option from the action
menu the original text is substituted in the Emacs buffer.
If an element is a cons cell it must be one of
(\"Menu item\" . \"substitute string\")
(\"Menu item\" . some-function)
The \"car\" value of the cons cell must be a string. The string
is shown in the action menu as one of the options. The \"cdr\"
value of the cons cell defines the action that is taken for the
menu option. If the \"cdr\" value is a string then that string is
the substitute string. If the \"cdr\" value is a function (a
symbol or a lambda expression) then that function is called when
user chooses the menu option. The function is called with one
argument: a vector returned by `wcheck-marked-text-at' function
for the marked text in question.
Effectively `action-program' function or `action-program'
executable program with `action-parser' function provide a
feature that can offer spelling suggestions for user: just return
suggestions as a list of strings. Alternatively they can offer
any kind of useful actions by calling custom functions. There are
a lot of possibilities.
For configuration examples, see the README file in URL
`https://github.com/tlikonen/wcheck-mode'."
:group 'wcheck
:type
`(repeat
(list :format "%v"
(string :tag "Language")
(repeat :inline t
:tag "Options"
,wcheck--language-data-customize-interface))))
;;;###autoload
(defconst wcheck--language-data-defaults-hard-coded
'((parser . wcheck-parser-lines)
(connection . nil)
(face . wcheck-default-face)
(syntax . text-mode-syntax-table)
(regexp-start . "\\<'*")
(regexp-body . "\\w+?")
(regexp-end . "'*\\>")
(regexp-discard . "\\`'+\\'")
(case-fold . nil)
(read-or-skip-faces (nil))
(action-autoselect . nil))
"Hard-coded default language configuration for `wcheck-mode'.
This constant is for Wcheck mode's internal use only. This
provides useful defaults if both `wcheck-language-data' and
`wcheck-language-data-defaults' fail.")
;;;###autoload
(defcustom wcheck-language-data-defaults
wcheck--language-data-defaults-hard-coded
"Default language configuration for `wcheck-mode'.
These default values are used when language-specific settings
don't provide a valid value. `wcheck-mode' will choose some
useful defaults even if this variable is not (properly) set. See
variable `wcheck-language-data' for information about possible
settings.
Here's an example value for the variable:
((parser . wcheck-parser-lines)
(action-parser . wcheck-parser-ispell-suggestions)
(connection . nil)
(face . wcheck-default-face)
(syntax . text-mode-syntax-table)
(regexp-start . \"\\\\=\\<\\='*\")
(regexp-body . \"\\\\w+?\")
(regexp-end . \"\\='*\\\\=\\>\")
(regexp-discard . \"\\\\\\=`\\='+\\\\\\='\")
(case-fold . nil)
(read-or-skip-faces
((emacs-lisp-mode c-mode) read
font-lock-comment-face font-lock-doc-face)
(message-mode read nil
message-header-subject message-cited-text)))"
:group 'wcheck
:type `(repeat ,wcheck--language-data-customize-interface))
;;;###autoload
(defcustom wcheck-language ""
"Default language for `wcheck-mode'.
Normally the global value defines the language for new buffers.
If a buffer-local value exists it is used instead. This variable
becomes automatically buffer-local when `wcheck-mode' is turned
on in a buffer, so changing the global value does not affect
buffers which already have `wcheck-mode' turned on.
User is free to set this variable directly (e.g., in programs)
but in interactive use it is usually better to use the command
`\\[wcheck-change-language]' instead. The command can change
language immediately while `wcheck-mode' is turned on, whereas
changing just the value of this variable takes effect only when
`wcheck-mode' is turned on next time."
:type '(string :tag "Default language")
:group 'wcheck)
(make-variable-buffer-local 'wcheck-language)
;;;###autoload
(defface wcheck-default-face
'((t (:underline "red")))
"Default face for marking strings in a buffer.
This is used when language does not define a face."
:group 'wcheck)
;;; Variables
(defvar wcheck-mode nil)
(defvar wcheck-mode-map (make-sparse-keymap)
"Keymap for `wcheck-mode'.")
(defvar wcheck--timer nil)
(defvar wcheck--timer-idle .3
"`wcheck-mode' idle timer delay (in seconds).")
(defvar wcheck--timer-paint-event-count 0)
(defvar wcheck--timer-paint-event-count-std 3
"Run buffer paint event this many times in a row.
With too low values all data from external processes may not have
arrived and window gets only partially painted. A higher value
increases the probability that windows get fully painted but it
also makes `wcheck-jump-forward' and `wcheck-jump-backward'
slower. A suitable compromise may be 3 or 4.")
(defvar wcheck--change-language-history nil
"Language history for command `wcheck-change-language'.")
(defvar wcheck--buffer-data nil)
(defvar wcheck--jump-step 5000)
;;; Macros
(defmacro wcheck--define-condition (name superclass &optional message)
(declare (indent defun))
`(progn
(put ',name 'error-conditions
(append (get ',superclass 'error-conditions) (list ',name)))
(put ',name 'error-message ,message)
',name))
(defmacro wcheck--loop-over-reqs-engine (key var &rest body)
`(dolist (,var (delq nil (mapcar (lambda (buffer)
(when (wcheck--buffer-data-get
:buffer buffer ,key)
buffer))
(wcheck--buffer-data-get-all :buffer))))
(when (buffer-live-p ,var)
(with-current-buffer ,var
,@body))))
(defmacro wcheck--loop-over-read-reqs (var &rest body)
(declare (indent 1))
`(wcheck--loop-over-reqs-engine :read-req ,var ,@body))
(defmacro wcheck--loop-over-paint-reqs (var &rest body)
(declare (indent 1))
`(wcheck--loop-over-reqs-engine :paint-req ,var ,@body))
(defmacro wcheck--loop-over-jump-reqs (var &rest body)
(declare (indent 1))
`(wcheck--loop-over-reqs-engine :jump-req ,var ,@body))
(defmacro wcheck--with-language-data (var-lang bindings &rest body)
(declare (indent 2))
(let ((language (make-symbol "--wck-language--")))
`(let* ((,language ,(cadr var-lang))
,@(when (car var-lang)
`((,(car var-lang) ,language)))
,@(mapcar
(lambda (var)
(cond ((symbolp var)
(list var `(wcheck-query-language-data
,language ',var)))
((and var (listp var))
(list (car var) `(wcheck-query-language-data
,language ',(cadr var))))))
bindings))
,@body)))
;;; Conditions
(wcheck--define-condition wcheck--error error)
(wcheck--define-condition wcheck--language-does-not-exist-error wcheck--error)
(wcheck--define-condition wcheck--program-not-configured-error wcheck--error)
(wcheck--define-condition wcheck--not-a-list-of-strings-error wcheck--error)
(wcheck--define-condition wcheck--funcall-error wcheck--error)
(wcheck--define-condition wcheck--action-error wcheck--error)
(wcheck--define-condition wcheck--action-program-error wcheck--action-error)
(wcheck--define-condition wcheck--parser-function-not-configured-error
wcheck--action-error)
(wcheck--define-condition wcheck--overlay-not-found-error wcheck--error)
;;; Interactive commands
;;;###autoload
(defun wcheck-change-language (language &optional global)
"Change language for current buffer (or globally).
Change `wcheck-mode' language to LANGUAGE. The change is
buffer-local but if GLOBAL is non-nil (prefix argument if called
interactively) then change the global default language."
(interactive
(let* ((comp (mapcar #'car wcheck-language-data))
(default (cond ((and current-prefix-arg
(member (default-value 'wcheck-language) comp))
(default-value 'wcheck-language))
((member wcheck-language comp)
wcheck-language)
((car comp))
(t ""))))
(list (completing-read
(format (if current-prefix-arg
"Global default language (%s): "
"Language for the current buffer (%s): ")
default)
comp nil t nil 'wcheck--change-language-history default)
current-prefix-arg)))
(condition-case error-data
(when (stringp language)
;; Change the language, locally or globally, and update buffer
;; database, if needed.
(if global
;; Just change the global value and leave.
(setq-default wcheck-language language)
;; Change the buffer-local value.
(setq wcheck-language language)
;; If the mode is currently turned on check if language's
;; checker program or function is configured and if all is OK
;; request update for the buffer.
(when wcheck-mode
(if (wcheck--program-configured-p wcheck-language)
;; It's OK; update the buffer.
(progn
(wcheck--buffer-lang-proc-data-update
(current-buffer) wcheck-language)
(wcheck--buffer-data-set (current-buffer) :read-req t)
(wcheck--remove-overlays))
(signal 'wcheck--program-not-configured-error wcheck-language))))
;; Return the language.
language)
(wcheck--program-not-configured-error
(wcheck-mode -1)
(message "Checker program is not configured for language \"%s\""
(cdr error-data)))))
(defun wcheck--mode-turn-on ()
;; Turn the mode on, but first some checks.
(let ((buffer (current-buffer))
(language wcheck-language))
(condition-case error-data
(cond
((minibufferp buffer)
(signal 'wcheck--error "Can't use `wcheck-mode' in a minibuffer"))
((not (wcheck--language-exists-p language))
(signal 'wcheck--language-does-not-exist-error language))
((not (wcheck--program-configured-p language))
(signal 'wcheck--program-not-configured-error language))
(t
(make-local-variable 'wcheck-language)
(wcheck--add-local-hooks buffer)
(wcheck--add-global-hooks)
(wcheck--buffer-lang-proc-data-update buffer language)
(wcheck--timer-start)
(wcheck--buffer-data-set buffer :read-req t)))
(wcheck--program-not-configured-error
(wcheck-mode -1)
(message "Checker program is not configured for language \"%s\""
(cdr error-data)))
(wcheck--language-does-not-exist-error
(wcheck-mode -1)
(message "Language \"%s\" does not exist" (cdr error-data))))))
(defun wcheck--mode-turn-off ()
(let ((buffer (current-buffer)))
;; We clear overlays form the buffer, remove the buffer from buffer
;; database.
(wcheck--remove-overlays)
(wcheck--buffer-lang-proc-data-update buffer nil)
;; If there are no buffers using wcheck-mode anymore, stop the idle
;; timer and remove global hooks.
(when (null (wcheck--buffer-data-get-all :buffer))
(wcheck--timer-stop)
(wcheck--remove-global-hooks))
(wcheck--remove-local-hooks buffer)))
(defun wcheck--mode-line-lang ()
(condition-case nil
(let (lang-code)
(catch 'enough
(mapc (lambda (c)
(when (char-equal ?w (char-syntax c))
(push c lang-code)
(when (>= (length lang-code) 2)
(throw 'enough t))))
(wcheck--buffer-data-get :buffer (current-buffer) :language)))
(apply #'string (nreverse lang-code)))
(error "")))
;;;###autoload
(define-minor-mode wcheck-mode
"General interface for text checkers.
With optional (prefix) ARG turn on the mode if ARG is positive,
otherwise turn it off. If ARG is not given toggle the mode.
Wcheck is a minor mode for automatically checking and marking
strings in Emacs buffer. Wcheck sends (parts of) buffer's content
to a text-checker back-end and, relying on its output, decides if
some parts of text should be marked.
Wcheck can be used with external spell-checker programs such as
Ispell and Enchant, but actually any tool that can receive text
stream from standard input and send text to standard output can
be used. The checker back-end can also be an Emacs Lisp function.
Different configuration units are called \"languages\". See the
documentation of variables `wcheck-language-data',
`wcheck-language-data-defaults' and `wcheck-language' for
information on how to configure Wcheck mode. You can access and
configure the variables through customize group `wcheck'.
Interactive command `wcheck-change-language' is used to switch
languages. Command `wcheck-actions' gives an action menu for the
marked text at point (also accessible through the right-click
mouse menu). Commands `wcheck-jump-forward' and
`wcheck-jump-backward' move point to next/previous marked text
area.
A note for Emacs Lisp programmers: Emacs Lisp function
`wcheck-marked-text-at' returns information about marked text at
a buffer position. Function `wcheck-query-language-data' can be
used for querying effective configuration data for any language."
:init-value nil
:lighter (" W:" (:eval (wcheck--mode-line-lang)))
:keymap wcheck-mode-map
(condition-case error-data
(if wcheck-mode
(wcheck--mode-turn-on)
(wcheck--mode-turn-off))
(wcheck--error
(wcheck-mode -1)
(message "%s" (cdr error-data)))))
;;; Timers
(defun wcheck--timer-start ()
"Start `wcheck-mode' idle timer if it's not running already."
(unless wcheck--timer
(setq wcheck--timer
(run-with-idle-timer wcheck--timer-idle t
#'wcheck--timer-read-event))))
(defun wcheck--timer-stop ()
"Stop `wcheck-mode' idle timer."
(when wcheck--timer
(cancel-timer wcheck--timer)
(setq wcheck--timer nil)))
(defun wcheck--funcall-after-idle (function &rest args)
(apply #'run-with-idle-timer
(+ wcheck--timer-idle (wcheck--current-idle-time-seconds))
nil function args))
(defun wcheck--timer-paint-event-run (&optional count)
(if (integerp count)
(let ((at-least (max count wcheck--timer-paint-event-count)))
(if (> wcheck--timer-paint-event-count 0)
(setq wcheck--timer-paint-event-count at-least)
(setq wcheck--timer-paint-event-count at-least)
(wcheck--funcall-after-idle #'wcheck--timer-paint-event)))
(if (> (setq wcheck--timer-paint-event-count
(1- wcheck--timer-paint-event-count))
0)
(wcheck--funcall-after-idle #'wcheck--timer-paint-event)
(wcheck--timer-jump-event))))
(defun wcheck--force-read (buffer)
(redisplay t)
(wcheck--buffer-data-set buffer :read-req t)
(wcheck--timer-read-event))
(defun wcheck--timer-read-event ()
"Send windows' content to checker program or function.
This function is usually called by the `wcheck-mode' idle timer.
The function walks through all windows which belong to buffers
that have requested update. It reads windows' content and sends
it checker program or function associated with the buffer's
language. Finally, this function starts another idle timer for
marking strings in buffers."
(wcheck--loop-over-read-reqs buffer
(unless (wcheck--buffer-data-get :buffer buffer :jump-req)
;; We are about to fulfill buffer's window-reading request so
;; remove the request. Reset also the list of received strings and
;; visible window areas.
(wcheck--buffer-data-set buffer :read-req nil)
(wcheck--buffer-data-set buffer :strings nil)
(wcheck--buffer-data-set buffer :areas nil)
;; Walk through all windows which belong to this buffer.
(let (area-alist strings)
(walk-windows (lambda (window)