ispell.c revision f765521f
1/*
2 * Copyright (c) 1999 by The XFree86 Project, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
19 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 *
22 * Except as contained in this notice, the name of the XFree86 Project shall
23 * not be used in advertising or otherwise to promote the sale, use or other
24 * dealings in this Software without prior written authorization from the
25 * XFree86 Project.
26 *
27 * Author: Paulo César Pereira de Andrade
28 */
29
30/* $XdotOrg: xc/programs/xedit/ispell.c,v 1.6 2004/12/04 00:43:13 kuhn Exp $ */
31/* $XFree86: xc/programs/xedit/ispell.c,v 1.19 2002/10/19 20:04:20 herrb Exp $ */
32
33#include "xedit.h"
34#include "util.h"
35#include <stdlib.h>
36#include <unistd.h>
37#include <fcntl.h>
38#include <signal.h>
39#include <ctype.h>
40#include <locale.h>
41#include <errno.h>
42#include <sys/types.h>
43#include <sys/wait.h>
44#include <X11/Xaw/Toggle.h>
45#include <X11/Xaw/MenuButton.h>
46#include <X11/Xaw/SmeBSB.h>
47#include <X11/Xaw/SimpleMenu.h>
48#include <X11/Xos.h>
49
50#define RECEIVE		1
51#define SEND		2
52
53#define CHECK		0
54#define	ADD		1
55#define REMOVE		2
56
57#define	ASIS		1
58#define UNCAP		2
59
60/*
61 * Types
62 */
63#define UNDO_DEPTH	16
64typedef struct _ispell_undo {
65    char *undo_str;
66    int undo_count;
67    XawTextPosition undo_pos;
68    Boolean repeat;	/* two (misspelled?) words together */
69    Boolean terse;
70    int format;		/* remember text formatting style */
71    struct _ispell_undo *next, *prev;
72} ispell_undo;
73
74typedef struct _ispell_dict {
75    Widget sme;
76    char *wchars;
77    struct _ispell_dict *next;
78} ispell_dict;
79
80#define	TEXT	0
81#define HTML	1
82struct _ispell_format {
83    char *name;
84    int value;
85    Widget sme;
86};
87
88static struct _ispell_format ispell_format[] = {
89    {"text",	TEXT},
90    {"html",	HTML},
91};
92
93struct _ispell {
94    Widget shell, form, mispelled, repeated, word, replacement, text,
95	   suggestions, viewport, list, commands, replace, status,
96	   replaceAll, undo, ignore, ignoreAll, add, addUncap, suspend,
97	   cancel, check, look, terse, options, dict, dictMenu,
98	   format, formatMenu;
99
100    Widget ascii, source;
101    XtInputId id;
102    int pid, ifd[2], ofd[2];
103    XawTextPosition left, right;
104    char *item;
105    Bool lock;
106    Bool repeat;
107    Bool checkit;
108    int stat;
109    char *buf;
110    int bufsiz;
111    int buflen;
112    char sendbuf[1024];
113    char sentbuf[1024];
114
115    int undo_depth;
116    ispell_undo *undo_head, *undo_base;
117    char *undo_for;
118
119    char *wchars;
120    char *cmd;
121    char *skip;
122    char *command;
123    Boolean terse_mode, undo_terse_mode;
124    char *guess_label, *miss_label, *root_label, *none_label, *eof_label,
125	 *compound_label, *ok_label, *repeat_label, *working_label, *look_label;
126    char *look_cmd;
127    char *words_file;
128
129    char *dictionary;
130    char *dict_list;
131    ispell_dict *dict_info;
132
133    int format_mode;	/* to undo correctly */
134    char *formatting;
135    struct _ispell_format *format_info;
136};
137
138typedef struct _ReplaceEntry ReplaceEntry;
139struct _ReplaceEntry {
140    hash_key	*word;
141    ReplaceEntry*next;
142    char	*replace;
143};
144
145typedef struct _IgnoreEntry IgnoreEntry;
146struct _IgnoreEntry {
147    hash_key	*word;
148    IgnoreEntry	*next;
149    int		add;
150};
151
152/*
153 * Prototypes
154 */
155static void AddIspell(Widget, XtPointer, XtPointer);
156static void ChangeDictionaryIspell(Widget, XtPointer, XtPointer);
157static void ChangeFormatIspell(Widget, XtPointer, XtPointer);
158static void CheckIspell(Widget, XtPointer, XtPointer);
159static void IgnoreIspell(Widget, XtPointer, XtPointer);
160static Bool InitIspell(void);
161static void IspellCheckUndo(void);
162static int IspellConvertHtmlAmp(char*);
163static Bool IspellDoIgnoredWord(char*, int, int);
164static Bool IspellIgnoredWord(char*, int, int);
165static void IspellInputCallback(XtPointer, int*, XtInputId*);
166static void IspellKillUndoBuffer(void);
167static Bool IspellReceive(void);
168static char *IspellReplacedWord(char*, char*);
169static int IspellSend(void);
170static void IspellSetSelection(XawTextPosition, XawTextPosition);
171static void IspellSetRepeated(Bool);
172static void IspellSetSensitive(Bool);
173static void IspellSetStatus(char*);
174static void IspellSetTerseMode(Bool);
175static Bool IspellStartProcess(void);
176static Bool IspellCheckProcess(void);
177static Bool IspellEndProcess(Bool, Bool);
178static void LookIspell(Widget, XtPointer, XtPointer);
179static void PopdownIspell(Widget, XtPointer, XtPointer);
180static void ReplaceIspell(Widget, XtPointer, XtPointer);
181static void RevertIspell(Widget, XtPointer, XtPointer);
182static void SelectIspell(Widget, XtPointer, XtPointer);
183static void ToggleTerseIspell(Widget, XtPointer, XtPointer);
184static void timeout_signal(int);
185static void (*old_timeout)(int);
186static void UndoIspell(Widget, XtPointer, XtPointer);
187
188Bool _XawTextSrcUndo(TextSrcObject, XawTextPosition*);
189
190/*
191 * Initialization
192 */
193static struct _ispell ispell;
194
195#define RSTRTBLSZ	23
196#define ISTRTBLSZ	71
197static hash_table *replace_hash;
198static hash_table *ignore_hash;
199
200#ifndef XtCStatus
201#define XtCStatus	"Status"
202#endif
203
204#define Offset(field) XtOffsetOf(struct _ispell, field)
205static XtResource resources[] = {
206    {"wordChars", "Chars", XtRString, sizeof(char*),
207	Offset(wchars), XtRString, ""},
208    {"ispellCommand", "CommandLine", XtRString, sizeof(char*),
209	Offset(cmd), XtRString, "/usr/local/bin/ispell"},
210    {"terseMode", "Terse", XtRBoolean, sizeof(Boolean),
211	Offset(terse_mode), XtRImmediate, (XtPointer)False},
212    {"guessLabel", XtCStatus, XtRString, sizeof(String),
213	Offset(guess_label), XtRString, "Guess"},
214    {"missLabel", XtCStatus, XtRString, sizeof(String),
215	Offset(miss_label), XtRString, "Miss"},
216    {"rootLabel", XtCStatus, XtRString, sizeof(String),
217	Offset(root_label), XtRString, "Root:"},
218    {"noneLabel", XtCStatus, XtRString, sizeof(String),
219	Offset(none_label), XtRString, "None"},
220    {"compoundLabel", XtCStatus, XtRString, sizeof(String),
221	Offset(compound_label), XtRString, "Compound"},
222    {"okLabel", XtCStatus, XtRString, sizeof(String),
223	Offset(ok_label), XtRString, "Ok"},
224    {"eofLabel", XtCStatus, XtRString, sizeof(String),
225	Offset(eof_label), XtRString, "End Of File"},
226    {"repeatLabel", XtCStatus, XtRString, sizeof(String),
227	Offset(repeat_label), XtRString, "Repeat"},
228    {"workingLabel", XtCStatus, XtRString, sizeof(String),
229	Offset(working_label), XtRString, "..."},
230    {"lookLabel", XtCStatus, XtRString, sizeof(String),
231	Offset(look_label), XtRString, "Look"},
232    {"lookCommand", "CommandLine", XtRString, sizeof(char*),
233	Offset(look_cmd), XtRString, "/usr/bin/egrep -i"},
234    {"wordsFile", "Words", XtRString, sizeof(char*),
235	Offset(words_file), XtRString, "/usr/share/dict/words"},
236    {"dictionary", "Dictionary", XtRString, sizeof(char*),
237	Offset(dictionary), XtRString, "american"},
238    {"dictionaries", "Dictionary", XtRString, sizeof(char*),
239	Offset(dict_list), XtRString, "american americanmed+ english"},
240    {"formatting", "TextFormat", XtRString, sizeof(char*),
241	Offset(formatting), XtRString, "text"},
242};
243#undef Offset
244
245#ifdef NO_LIBC_I18N
246static int
247ToLower(int ch)
248{
249    char buf[2];
250
251    *buf = ch;
252    XmuNCopyISOLatin1Lowered(buf, buf, sizeof(buf));
253
254    return (*buf);
255}
256
257static int
258ToUpper(int ch)
259{
260    char buf[2];
261
262    *buf = ch;
263    XmuNCopyISOLatin1Uppered(buf, buf, sizeof(buf));
264
265    return (*buf);
266}
267
268static int
269IsLower(int ch)
270{
271    char upbuf[2];
272    char lobuf[2];
273
274    *upbuf = *lobuf = ch;
275    XmuNCopyISOLatin1Lowered(lobuf, lobuf, sizeof(lobuf));
276    XmuNCopyISOLatin1Uppered(upbuf, upbuf, sizeof(upbuf));
277
278    return (*lobuf != *upbuf && ch == *lobuf);
279}
280
281static int
282IsUpper(int ch)
283{
284    char upbuf[2];
285    char lobuf[2];
286
287    *upbuf = *lobuf = ch;
288    XmuNCopyISOLatin1Lowered(lobuf, lobuf, sizeof(lobuf));
289    XmuNCopyISOLatin1Uppered(upbuf, upbuf, sizeof(upbuf));
290
291    return (*lobuf != *upbuf && ch == *upbuf);
292}
293#else
294#define	ToLower	tolower
295#define ToUpper	toupper
296#define IsLower islower
297#define IsUpper isupper
298#endif
299
300/*
301 * Implementation
302 */
303#ifdef STDERR_FILENO
304# define WRITES(s) write(STDERR_FILENO, s, strlen(s))
305#else
306# define WRITES(s) write(fileno(stderr), s, strlen(s))
307#endif
308
309/*ARGSUSED*/
310static void
311timeout_signal(int unused)
312{
313    int olderrno = errno;
314
315    WRITES("Warning: Timeout waiting ispell process to die.\n");
316    kill(ispell.pid, SIGTERM);
317    errno = olderrno;
318}
319
320static void
321IspellSetSelection(XawTextPosition left, XawTextPosition right)
322{
323    /* Try to make sure the selected word is completely visible */
324    XawTextSetInsertionPoint(ispell.ascii, right);
325    XawTextSetInsertionPoint(ispell.ascii, left);
326    XawTextSetSelection(ispell.ascii, left, right);
327}
328
329static void
330IspellSetStatus(char *label)
331{
332    Arg args[1];
333
334    XtSetArg(args[0], XtNlabel, label);
335    XtSetValues(ispell.status, args, 1);
336}
337
338static void
339IspellSetRepeated(Bool state)
340{
341    static char *mispelled, *repeated;
342    Arg args[1];
343
344    if (mispelled == NULL) {
345	XtSetArg(args[0], XtNlabel, &mispelled);
346	XtGetValues(ispell.mispelled, args, 1);
347	mispelled = XtNewString(mispelled);
348    }
349    if (repeated == NULL) {
350	XtSetArg(args[0], XtNlabel, &repeated);
351	XtGetValues(ispell.repeated, args, 1);
352	repeated = XtNewString(repeated);
353    }
354    XtSetSensitive(ispell.replaceAll, !state);
355    XtSetSensitive(ispell.ignoreAll, !state);
356    XtSetSensitive(ispell.add, !state);
357    XtSetSensitive(ispell.addUncap, !state);
358    if (!state) {
359	XtSetArg(args[0], XtNlabel, mispelled);
360	XtSetValues(ispell.mispelled, args, 1);
361    }
362    else {
363	XtSetArg(args[0], XtNlabel, repeated);
364	XtSetValues(ispell.mispelled, args, 1);
365    }
366}
367
368static void
369IspellSetSensitive(Bool state)
370{
371    XtSetSensitive(ispell.replace, state);
372    XtSetSensitive(ispell.replaceAll, state);
373    XtSetSensitive(ispell.ignore, state);
374    XtSetSensitive(ispell.ignoreAll, state);
375    XtSetSensitive(ispell.add, state);
376    XtSetSensitive(ispell.addUncap, state);
377}
378
379static void
380IspellSetTerseMode(Bool mode)
381{
382    Arg args[1];
383
384    XtSetArg(args[0], XtNstate, ispell.terse_mode = mode);
385    XtSetValues(ispell.terse, args, 1);
386    write(ispell.ofd[1], mode ? "!\n" : "%\n", 2);
387}
388
389static void
390IspellCheckUndo(void)
391{
392    ispell_undo *undo = XtNew(ispell_undo);
393
394    if (ispell.undo_for && strcmp(ispell.undo_for, ispell.dictionary)) {
395	XeditPrintf("Undo: Dictionary changed. Previous undo information lost.\n");
396	IspellKillUndoBuffer();
397	Feep();
398    }
399
400    undo->next = NULL;
401    undo->repeat = False;
402    undo->terse = ispell.undo_terse_mode;
403    undo->format = ispell.format_mode;
404    if ((undo->prev = ispell.undo_head) != NULL)
405	undo->prev->next = undo;
406    else
407	undo->prev = NULL;
408    ++ispell.undo_depth;
409    if (!ispell.undo_base) {
410	ispell.undo_base = undo;
411	XtSetSensitive(ispell.undo, True);
412    }
413    else if (ispell.undo_depth > UNDO_DEPTH) {
414	ispell_undo *tmp;
415
416	if (ispell.undo_base->undo_str)
417	    XtFree(ispell.undo_base->undo_str);
418	tmp = ispell.undo_base->next;
419	XtFree((char*)ispell.undo_base);
420	tmp->prev = NULL;
421	ispell.undo_base = tmp;
422	ispell.undo_depth = UNDO_DEPTH;
423    }
424    ispell.undo_head = undo;
425}
426
427static char *
428IspellReplacedWord(char *word, char *replace)
429{
430    int			word_len;
431    hash_key		*word_key;
432    ReplaceEntry	*entry;
433
434    word_len = strlen(word);
435    entry = (ReplaceEntry *)hash_check(replace_hash, word, word_len);
436    if (entry == NULL) {
437	word_key = XtNew(hash_key);
438	word_key->value = XtNewString(word);
439	word_key->length = word_len;
440	entry = XtNew(ReplaceEntry);
441	entry->word = word_key;
442	entry->replace = NULL;
443	entry->next = NULL;
444	hash_put(replace_hash, (hash_entry *)entry);
445    }
446
447    if (replace) {
448	XtFree(entry->replace);
449	entry->replace = XtNewString(replace);
450    }
451
452    return (entry->replace);
453}
454
455static Bool
456IspellDoIgnoredWord(char *word, int cmd, int add)
457{
458    int		word_len;
459    hash_key	*word_key;
460    IgnoreEntry	*entry;
461
462    word_len = strlen(word);
463    entry = (IgnoreEntry *)hash_check(ignore_hash, word, word_len);
464    if (entry == NULL) {
465	if (cmd != ADD)
466	    return (False);
467
468	word_key = XtNew(hash_key);
469	word_key->value = XtNewString(word);
470	word_key->length = word_len;
471	entry = XtNew(IgnoreEntry);
472	entry->word = word_key;
473	entry->add = add;
474	entry->next = NULL;
475	hash_put(ignore_hash, (hash_entry *)entry);
476
477	return (True);
478    }
479    else if (cmd == REMOVE)
480	hash_rem(ignore_hash, (hash_entry *)entry);
481
482    return (cmd == CHECK);
483}
484
485static Bool
486IspellIgnoredWord(char *word, int cmd, int add)
487{
488    if (add != UNCAP && IspellDoIgnoredWord(word, cmd, add))
489	return (True);
490
491    /* add/remove uncapped word to/of list,
492     * or cheks for correct capitalization */
493    if (add == UNCAP || cmd == CHECK) {
494	unsigned char *str = (unsigned char*)word;
495	unsigned char string[1024];
496	Bool upper, status;
497	int i;
498
499	status = True;
500	upper = IsUpper(*str);
501	*string = upper ? ToLower(*str) : *str;
502	if (*str)
503	    str++;
504	if (IsLower(*str))
505	    upper = False;
506	for (i = 1; *str && i < sizeof(string) - 1; i++, str++) {
507	    if (upper && IsLower(*str))
508		status = False;
509	    else if (!upper && IsUpper(*str))
510		status = False;
511	    string[i] = ToLower(*str);
512	}
513	string[i] = '\0';
514
515	if ((cmd != CHECK || status) &&
516	    IspellDoIgnoredWord((char*)string, cmd, add))
517	    return (True);
518    }
519
520    return (False);
521}
522
523/*ARGSUSED*/
524static Bool
525IspellReceive(void)
526{
527    int i, len, old_len;
528    Arg args[2];
529    char *str, *end, **list, **old_list;
530    char *tmp, word[1024];
531    int j;
532
533    if (ispell.lock || ispell.stat != RECEIVE)
534	return (False);
535
536    while (1) {		/* read the entire line */
537	if (ispell.buflen >= ispell.bufsiz - 1)
538	    ispell.buf = XtRealloc(ispell.buf, ispell.bufsiz += BUFSIZ);
539	if ((len = read(ispell.ifd[0], &ispell.buf[ispell.buflen],
540			ispell.bufsiz - ispell.buflen - 1)) <= 0)
541	    break;
542	ispell.buflen += len;
543    }
544    if (ispell.buflen <= 0)
545	return (False);
546    len = 0;
547    i = ispell.buflen - 1;
548    while (i >= 0 && ispell.buf[i] == '\n') {
549	++len;
550	--i;
551    }
552    if (len < 2 - ((ispell.terse_mode && i == -1) || ispell.buf[0] == '@'))
553	return (False);
554    ispell.buf[ispell.buflen - len] = '\0';
555    ispell.buflen = 0;
556
557    if ((tmp = strchr(ispell.sendbuf, '\n')) != NULL)
558	*tmp = '\0';
559
560    switch (ispell.buf[0]) {
561	case '&':	/* MISS */
562	case '?':	/* GUESS */
563	    str = strchr(&ispell.buf[2], ' ');
564	    if (!ispell.checkit) {
565		*str = '\0';
566		XtSetArg(args[0], XtNlabel, &ispell.buf[2]);
567		XtSetValues(ispell.word, args, 1);
568	    }
569	    ++str;
570	    list = NULL;
571	    str = strchr(str, ':') + 1;
572	    for (i = 0; ; i++) {
573		end = strchr(str, ',');
574		if (end)	*end = '\0';
575		if ((i % 16) == 0)
576		    list = (char**)XtRealloc((char*)list, (i + 16) * sizeof(char*));
577		tmp = word;
578		for (j = 1; j < sizeof(word) && str[j]; j++) {
579		    if (str[j] == '+')
580			continue;
581		    else if (str[j] == '-' && str[j+1] != '-' && str[j-1] != '-') {
582			char *p, string[256];
583			int k, l;
584
585			for (l = 0, k = j + 1; str[k] != '+' && str[k] != '-'
586			     && str[k] && l < sizeof(string) - 1; k++, l++)
587			    string[l] = str[k];
588			string[l] = '\0';
589			*tmp = '\0';
590			if (l && (p = strstr(word, string)) != NULL) {
591			    char *sav = p;
592
593			    while ((p = strstr(p + l, string)) != NULL)
594				sav = p;
595			    p = sav;
596			    if (strcmp(p, string) == 0) {
597				tmp = p;
598				j = k - 1;
599			    }
600			    else
601				*tmp++ = '-';
602			}
603			else
604			    *tmp++ = '-';
605		    }
606		    else
607			*tmp++ = str[j];
608		}
609		*tmp = '\0';
610		list[i] = XtNewString(word);
611
612		if (end)	str = end + 1;
613		else		break;
614	    }
615	    len = i + 1;
616
617	    XtSetArg(args[0], XtNlist, &old_list);
618	    XtSetArg(args[1], XtNnumberStrings, &old_len);
619	    XtGetValues(ispell.list, args, 2);
620
621	    ispell.item = NULL;
622	    if ((str = IspellReplacedWord(&ispell.buf[2], NULL)) != NULL)
623		for (i = 0; i < len; i++) {
624		    if (strcmp(list[i], str) == 0) {
625			ispell.item = list[i];
626			break;
627		    }
628		}
629	    else
630		ispell.item = list[i = 0];
631	    if (!ispell.item) {
632		list = (char**)XtRealloc((char*)list, (len + 1) * sizeof(char*));
633		ispell.item = list[i] = XtNewString(str);
634		++len;
635	    }
636
637	    XtSetArg(args[0], XtNlist, list);
638	    XtSetArg(args[1], XtNnumberStrings, len);
639	    XtSetValues(ispell.list, args, 2);
640
641	    XtSetSensitive(ispell.list, True);
642	    if (!ispell.checkit)
643		XawListHighlight(ispell.list, i);
644
645	    if (old_len > 1 || (XtName(ispell.list) != old_list[0])) {
646		while (--old_len > -1)
647		    XtFree(old_list[old_len]);
648		XtFree((char*)old_list);
649	    }
650
651	    if (!ispell.checkit) {
652		XtSetArg(args[0], XtNstring, ispell.item);
653		XtSetValues(ispell.text, args, 1);
654		IspellSetSelection(ispell.left, ispell.right);
655		if (ispell.repeat)
656		    IspellSetRepeated(ispell.repeat = False);
657	    }
658
659	    IspellSetStatus(ispell.buf[0] == '?' ?
660			    ispell.guess_label : ispell.miss_label);
661	    ispell.undo_terse_mode = ispell.terse_mode;
662	    ispell.format_mode = ispell.format_info->value;
663	    ispell.lock = True;
664	    break;
665	case '#':	/* NONE */
666	case '-':	/* COMPOUND */
667	case '+':	/* ROOT */
668	check_label:
669	    str = &ispell.sendbuf[1];
670	    if (!ispell.checkit) {
671		XtSetArg(args[0], XtNlabel, str);
672		XtSetValues(ispell.word, args, 1);
673	    }
674
675	    XtSetArg(args[0], XtNlist, &old_list);
676	    XtSetArg(args[1], XtNnumberStrings, &old_len);
677	    XtGetValues(ispell.list, args, 2);
678	    ispell.item = NULL;
679
680	    list = (char**)XtMalloc(sizeof(char**));
681	    if ((tmp = IspellReplacedWord(str, NULL)) != NULL)
682		str = tmp;
683	    if (tmp == NULL && ispell.buf[0] == '#')
684		list[0] = XtNewString("");
685	    else
686		list[0] = XtNewString(str);
687
688	    XtSetArg(args[0], XtNlist, list);
689	    XtSetArg(args[1], XtNnumberStrings, 1);
690	    XtSetValues(ispell.list, args, 2);
691
692	    if (tmp == NULL && ispell.buf[0] == '#') {
693		XawListUnhighlight(ispell.list);
694		XtSetSensitive(ispell.list, False);
695	    }
696	    else {
697		XtSetSensitive(ispell.list, True);
698		if (!ispell.checkit)
699		    XawListHighlight(ispell.list, 0);
700	    }
701	    if (old_len > 1 || (XtName(ispell.list) != old_list[0])) {
702		while (--old_len > -1)
703		    XtFree(old_list[old_len]);
704		XtFree((char*)old_list);
705	    }
706
707	    if (!ispell.checkit) {
708		XtSetArg(args[0], XtNstring, str);
709		XtSetValues(ispell.text, args, 1);
710		IspellSetSelection(ispell.left, ispell.right);
711		if (ispell.repeat)
712		    IspellSetRepeated(ispell.repeat = False);
713	    }
714
715	    ispell.undo_terse_mode = ispell.terse_mode;
716	    ispell.format_mode = ispell.format_info->value;
717	    ispell.lock = True;
718	    if (ispell.buf[0] == '+') {
719		if ((tmp = strchr(&ispell.buf[2], '\n')) != NULL)
720		    *tmp = '\0';
721		XmuSnprintf(word, sizeof(word), "%s %s",
722			    ispell.root_label, &ispell.buf[2]);
723		IspellSetStatus(word);
724	    }
725	    else
726		IspellSetStatus(ispell.buf[0] == '#' ? ispell.none_label :
727				ispell.buf[0] == '-' ? ispell.compound_label :
728				ispell.ok_label);
729	    break;
730	case '*':	/* OK */
731	case '\0':	/* when running in terse mode */
732	    if (!ispell.checkit)
733		(void)IspellIgnoredWord(&ispell.sendbuf[1], ADD, 0);
734	    else
735		goto check_label;
736	    ispell.lock = False;
737	    break;
738	case '@':	/* Ispell banner */
739	    /* it only happens when the dictionary is changed */
740	    if (!ispell.repeat) {
741		XawTextPosition left, right;
742
743		ispell.stat = SEND;
744		while (IspellSend() == 0)
745		    ;
746		/* word chars may have changed */
747		XawTextGetSelectionPos(ispell.ascii, &left, &right);
748		if (left != ispell.left || right != ispell.right) {
749		    XtSetArg(args[0], XtNstring, &ispell.sendbuf[1]);
750		    XtSetValues(ispell.text, args, 1);
751		    IspellSetSelection(ispell.left, ispell.right);
752		}
753		ispell.checkit = True;
754	    }
755	    else {
756		IspellSetStatus(ispell.repeat_label);
757		ispell.undo_terse_mode = ispell.terse_mode;
758		ispell.format_mode = ispell.format_info->value;
759		ispell.lock = True;
760		return (True);
761	    }
762	    break;
763	default:
764	    fprintf(stderr, "Unknown ispell command '%c'\n", ispell.buf[0]);
765	    return (False);
766    }
767
768    if (!ispell.lock && !ispell.checkit) {
769	ispell.stat = SEND;
770	while (IspellSend() == 0)
771	    ;
772    }
773
774    return (True);
775}
776
777static int
778IspellConvertHtmlAmp(char *buf)
779{
780    int len, ch = '?';
781
782    /* this function is static, so I can do it */
783    *strchr(++buf, ';') = '\0';
784
785    len = strlen(buf);
786    if (len == 0)
787	return ('&');
788    if (len > 1) {
789	if (strcasecmp(&buf[1], "lt") == 0)
790	    ch = '<';
791	else if (strcasecmp(&buf[1], "gt") == 0)
792	    ch = '>';
793	else if (strcasecmp(&buf[1], "nbsp") == 0)
794	    ch = ' ';
795	else if (strcasecmp(&buf[1], "amp") == 0)
796	    ch = '&';
797	else if (strcasecmp(&buf[1], "quot") == 0)
798	    ch = '"';
799	else if (*buf == '#') {
800	    char *tmp;
801
802	    if (len == 1)
803		return ('?');
804	    ch = strtol(&buf[1], &tmp, 10);
805	    if (*tmp)
806		fprintf(stderr, "Warning: bad html interpreting '&#' mark.\n");
807	}
808	else if (strcmp(&buf[1], "acute") == 0) {
809	    switch (*buf) {
810		case 'a': ch = 0xe1; break;
811		case 'e': ch = 0xe9; break;
812		case 'i': ch = 0xed; break;
813		case 'o': ch = 0xf3; break;
814		case 'u': ch = 0xfa; break;
815		case 'A': ch = 0xc1; break;
816		case 'E': ch = 0xc9; break;
817		case 'I': ch = 0xcd; break;
818		case 'O': ch = 0xd3; break;
819		case 'U': ch = 0xda; break;
820	    }
821	}
822	else if (strcmp(&buf[1], "grave") == 0) {
823	    switch (*buf) {
824		case 'a': ch = 0xe0; break;
825		case 'e': ch = 0xe8; break;
826		case 'i': ch = 0xec; break;
827		case 'o': ch = 0xf2; break;
828		case 'u': ch = 0xf9; break;
829		case 'A': ch = 0xc0; break;
830		case 'E': ch = 0xc8; break;
831		case 'I': ch = 0xcc; break;
832		case 'O': ch = 0xd2; break;
833		case 'U': ch = 0xd9; break;
834	    }
835	}
836	else if (strcmp(&buf[1], "tilde") == 0) {
837	    switch (*buf) {
838		case 'a': ch = 0xe3; break;
839		case 'o': ch = 0xf5; break;
840		case 'n': ch = 0xf1; break;
841		case 'A': ch = 0xe3; break;
842		case 'O': ch = 0xd5; break;
843		case 'N': ch = 0xd1; break;
844	    }
845	}
846	else if (strcmp(&buf[1], "circ") == 0) {
847	    switch (*buf) {
848		case 'a': ch = 0xe2; break;
849		case 'e': ch = 0xea; break;
850		case 'i': ch = 0xee; break;
851		case 'o': ch = 0xf4; break;
852		case 'u': ch = 0xfb; break;
853		case 'A': ch = 0xc2; break;
854		case 'E': ch = 0xca; break;
855		case 'I': ch = 0xce; break;
856		case 'O': ch = 0xd4; break;
857		case 'U': ch = 0xdb; break;
858	    }
859	}
860	else if (strcmp(&buf[1], "cedil") == 0) {
861	    switch (*buf) {
862		case 'c': ch = 0xe7; break;
863		case 'C': ch = 0xc7; break;
864	    }
865	}
866	/* add more cases here */
867    }
868
869    return (ch);
870}
871
872/*ARGSUSED*/
873static int
874IspellSend(void)
875{
876    XawTextPosition position, old_left, pos;
877    XawTextBlock block;
878    int i, len, spaces, nls;
879    Bool nl, html, inside_html;
880    char ampbuf[32];
881    int amplen;
882
883    if (ispell.lock || ispell.stat != SEND)
884	return (-1);
885
886    len = 1;
887    ispell.sendbuf[0] = '^';	/* don't evaluate following characters as commands */
888
889    spaces = nls = 0;
890
891    html = ispell.format_info->value == HTML;
892    inside_html = False;
893    amplen = 0;
894
895    /* skip non word characters */
896    pos = position = ispell.right;
897    nl = False;
898    while (1) {
899	Bool done = False;
900	char mb[sizeof(wchar_t)];
901
902	retry_html_space:
903	position = XawTextSourceRead(ispell.source, position,
904				     &block, BUFSIZ);
905	if (block.length == 0) {	/* end of file */
906	    ispell.stat = 0;
907	    ispell.lock = True;
908	    XawTextSetInsertionPoint(ispell.ascii, ispell.right);
909	    XawTextUnsetSelection(ispell.ascii);
910	    IspellSetSensitive(False);
911	    IspellSetStatus(ispell.eof_label);
912	    return (-1);
913	}
914	for (i = 0; i < block.length; i++) {
915	    if (international)
916		wctomb(mb, ((wchar_t*)block.ptr)[i]);
917	    else
918		mb[0] = block.ptr[i];
919	    if (amplen) {
920		if (amplen + 2 >= sizeof(ampbuf)) {
921		    if (!ispell.terse_mode)
922			fprintf(stderr, "Warning: error interpreting '&' mark.\n");
923		    amplen = 0;
924		    position = pos + 1;
925		    goto retry_html_space;
926		}
927		else if ((ampbuf[amplen++] = *mb) == ';') {
928		    int ch;
929
930		    ampbuf[amplen] = '\0';
931		    ch = IspellConvertHtmlAmp(ampbuf);
932		    amplen = 0;
933		    if (isalpha(ch) ||
934			(ch && strchr(ispell.wchars, ch))) {
935			/* interpret it again */
936			ispell.right = pos;
937			i = 0;
938			done = True;
939			break;
940		    }
941		    else if ((ch == '\n' || isspace(ch)) && spaces >= 0)
942			++spaces;
943		    else
944			spaces = -1;
945		}
946	    }
947	    else if (html && *mb == '&') {
948		ampbuf[amplen++] = *mb;
949		pos = block.firstPos + i;
950		continue;
951	    }
952	    else if ((!html || !inside_html) && (isalpha(*mb) ||
953		(*mb && strchr(ispell.wchars, *mb)))) {
954		done = True;
955		break;
956	    }
957	    else if (!html && *mb == '\n') {
958		nl = True;
959		if (++nls > 1 && (!html || !inside_html))
960		    spaces = -1;
961		else if (spaces >= 0)
962		    ++spaces;
963	    }
964	    else if (nl) {
965		nl = False;
966		if (*mb && strchr(ispell.skip, *mb)) {
967		    position = ispell.right =
968			XawTextSourceScan(ispell.source, ispell.right + i,
969					  XawstEOL, XawsdRight, 1, False);
970		    i = 0;
971		    break;
972		}
973		else if (spaces >= 0 && isspace(*mb))
974		    ++spaces;
975		else
976		    spaces = -1;
977	    }
978	    else if (html && inside_html) {
979		if (*mb == '>')
980		    inside_html = False;
981	    }
982	    else if (html && *mb == '<')
983		inside_html = True;
984	    else if (spaces >= 0 && (isspace(*mb) || (html && *mb == '\n')))
985		++spaces;
986	    else
987		spaces = -1;
988	}
989
990	ispell.right += i;
991	if (done)
992	    break;
993    }
994
995    old_left = ispell.left;
996
997    /* read a word */
998    position = ispell.left = ispell.right;
999    while (1) {
1000	Bool done = False;
1001	char mb[sizeof(wchar_t)];
1002
1003	retry_html_word:
1004	position = XawTextSourceRead(ispell.source, position,
1005				     &block, BUFSIZ);
1006	if (block.length == 0 && len == 1) {	/* end of file */
1007	    ispell.stat = 0;
1008	    ispell.lock = True;
1009	    XawTextSetInsertionPoint(ispell.ascii, ispell.right);
1010	    XawTextUnsetSelection(ispell.ascii);
1011	    IspellSetSensitive(False);
1012	    IspellSetStatus(ispell.eof_label);
1013	    return (-1);
1014	}
1015	for (i = 0; i < block.length; i++) {
1016	    if (international)
1017		wctomb(mb, ((wchar_t*)block.ptr)[i]);
1018	    else
1019		mb[0] = block.ptr[i];
1020	    if (amplen) {
1021		if (amplen + 2 >= sizeof(ampbuf)) {
1022		    if (!ispell.terse_mode)
1023			fprintf(stderr, "Warning: error interpreting '&' mark.\n");
1024		    amplen = 0;
1025		    position = pos + 1;
1026		    if (strchr(ispell.wchars, '&')) {
1027			if (len + 1 >= sizeof(ispell.sendbuf) - 1) {
1028			    done = True;
1029			    fprintf(stderr, "Warning: word is too large!\n");
1030			    break;
1031			}
1032			ispell.sendbuf[len++] = '&';
1033			goto retry_html_word;
1034		    }
1035		    else {
1036			ispell.right = position;
1037			i = 0;
1038			done = True;
1039			break;
1040		    }
1041		}
1042		else if ((ampbuf[amplen++] = *mb) == ';') {
1043		    int ch;
1044
1045		    ampbuf[amplen] = '\0';
1046		    ch = IspellConvertHtmlAmp(ampbuf);
1047		    amplen = 0;
1048		    if (!isalpha(ch) &&
1049			(!ch || !strchr(ispell.wchars, ch))) {
1050			ispell.right = pos;
1051			i = 0;
1052			done = True;
1053			break;
1054		    }
1055		    *mb = ch;
1056		}
1057		else
1058		    continue;
1059	    }
1060	    else if (html && *mb == '&') {
1061		ampbuf[amplen++] = *mb;
1062		pos = block.firstPos + i;
1063		continue;
1064	    }
1065	    else if (!isalpha(*mb) && (!*mb || !strchr(ispell.wchars, *mb))) {
1066		done = True;
1067		break;
1068	    }
1069	    ispell.sendbuf[len] = *mb;
1070	    if (++len >= sizeof(ispell.sendbuf) - 1) {
1071		done = True;
1072		fprintf(stderr, "Warning: word is too large!\n");
1073		break;
1074	    }
1075	}
1076	ispell.right += i;
1077	if (done || block.length == 0)
1078	    break;
1079    }
1080
1081    ispell.sendbuf[len] = '\0';
1082
1083    if (spaces > 0 && spaces <= 32 && strcmp(ispell.sendbuf, ispell.sentbuf) == 0) {
1084	Arg args[2];
1085	int old_len;
1086	char **list, **old_list;
1087	char label[sizeof(ispell.sendbuf) + sizeof(ispell.sentbuf) + 32];
1088
1089	strcpy(label, &ispell.sendbuf[1]);
1090	for (i = 0; i < spaces; i++)
1091	    label[len + i - 1] = ' ';
1092	strcpy(&label[len + i - 1], &ispell.sendbuf[1]);
1093	XtSetArg(args[0], XtNlabel, label);
1094	XtSetValues(ispell.word, args, 1);
1095
1096	XtSetArg(args[0], XtNstring, &ispell.sendbuf[1]);
1097	XtSetValues(ispell.text, args, 1);
1098
1099	XtSetArg(args[0], XtNlist, &old_list);
1100	XtSetArg(args[1], XtNnumberStrings, &old_len);
1101	XtGetValues(ispell.list, args, 2);
1102	list = (char**)XtMalloc(sizeof(char**));
1103	list[0] = XtNewString(&ispell.sendbuf[1]);
1104	XtSetArg(args[0], XtNlist, list);
1105	XtSetArg(args[1], XtNnumberStrings, 1);
1106	XtSetValues(ispell.list, args, 2);
1107	XtSetSensitive(ispell.list, True);
1108	XawListHighlight(ispell.list, 0);
1109	if (old_len > 1 || (XtName(ispell.list) != old_list[0])) {
1110	    while (--old_len > -1)
1111		XtFree(old_list[old_len]);
1112	    XtFree((char*)old_list);
1113	}
1114
1115	IspellSetRepeated(True);
1116	IspellSetSelection(old_left, ispell.right);
1117	IspellSetStatus(ispell.repeat_label);
1118	ispell.repeat = ispell.lock = True;
1119
1120	return (1);
1121    }
1122    strcpy(ispell.sentbuf, ispell.sendbuf);
1123
1124    if (len <= 2 || IspellIgnoredWord(&ispell.sendbuf[1], CHECK, 0))
1125	return (0);
1126
1127    ispell.sendbuf[len++] = '\n';
1128
1129    write(ispell.ofd[1], ispell.sendbuf, len);
1130
1131    ispell.stat = RECEIVE;
1132
1133    return (1);
1134}
1135
1136/*ARGSUSED*/
1137static void
1138IspellInputCallback(XtPointer closure, int *source, XtInputId *id)
1139{
1140    if (ispell.right < 0) {
1141	int len;
1142	char buf[1024];
1143
1144	ispell.right = XawTextGetInsertionPoint(ispell.ascii);
1145	ispell.right = XawTextSourceScan(ispell.source, ispell.right,
1146					      XawstEOL, XawsdLeft, 1, True);
1147	len = read(ispell.ifd[0], buf, sizeof(buf));
1148	if (strncmp(buf, "@(#)", 4) == 0) {
1149	    Arg args[1];
1150
1151	    buf[len - 1] = '\0';
1152	    XtSetArg(args[0], XtNtitle, &buf[5]);
1153	    XtSetValues(ispell.shell, args, 1);
1154	}
1155	else
1156	    fprintf(stderr, "Error: is ispell talking with me?\n");
1157	IspellSetTerseMode(ispell.terse_mode);
1158	while (IspellSend() == 0)
1159	    ;
1160    }
1161    else if (ispell.source)
1162	IspellReceive();
1163}
1164
1165/*ARGSUSED*/
1166void
1167IspellCallback(Widget w, XtPointer client_data, XtPointer call_data)
1168{
1169    Cardinal zero = 0;
1170
1171    IspellAction(textwindow, NULL, NULL, &zero);
1172}
1173
1174/*ARGSUSED*/
1175void
1176IspellAction(Widget w, XEvent *event, String *params, Cardinal *num_params)
1177{
1178    Arg args[3];
1179    Cardinal num_args;
1180    char **strs, **list;
1181    int n_strs;
1182    Bool first_time = InitIspell();
1183
1184    if (*num_params == 1 && (params[0][0] == 'e' || params[0][0] == 'E')) {
1185	PopdownIspell(w, (XtPointer)True, NULL);
1186	return;
1187    }
1188
1189    if (!XtIsSubclass(w, textWidgetClass) || ispell.source) {
1190	Feep();
1191	return;
1192    }
1193
1194    ispell.source = XawTextGetSource(ispell.ascii = w);
1195
1196    if (first_time) {
1197	/* let the user choose the better position for the ispell window */
1198	Dimension width, height, b_width;
1199	Position x, y, max_x, max_y;
1200
1201	x = y = -1;
1202	if (event) {
1203	    switch (event->type) {
1204		case ButtonPress:
1205		case ButtonRelease:
1206		    x = event->xbutton.x_root;
1207		    y = event->xbutton.y_root;
1208		    break;
1209		case KeyPress:
1210		case KeyRelease:
1211		    x = event->xkey.x_root;
1212		    y = event->xkey.y_root;
1213		    break;
1214	    }
1215	}
1216	if (x < 0 || y < 0) {
1217	    Window r, c;
1218	    int rx, ry, wx, wy;
1219	    unsigned mask;
1220
1221	    XQueryPointer(XtDisplay(ispell.shell), XtWindow(ispell.shell),
1222			  &r, &c, &rx, &ry, &wx, &wy, &mask);
1223	    x = rx;
1224	    y = ry;
1225	}
1226
1227	num_args = 0;
1228	XtSetArg(args[num_args], XtNwidth, &width);		num_args++;
1229	XtSetArg(args[num_args], XtNheight, &height);		num_args++;
1230	XtSetArg(args[num_args], XtNborderWidth, &b_width);	num_args++;
1231	XtGetValues(ispell.shell, args, num_args);
1232
1233	width += b_width << 1;
1234	height += b_width << 1;
1235
1236	x -= (Position)(width >> 1);
1237	if (x < 0)
1238	    x = 0;
1239	if (x > (max_x = (Position)(XtScreen(w)->width - width)))
1240	    x = max_x;
1241
1242	y -= (Position)(height >> 1);
1243	if (y < 0)
1244	    y = 0;
1245	if (y > (max_y = (Position)(XtScreen(w)->height - height)))
1246	    y = max_y;
1247
1248	num_args = 0;
1249	XtSetArg(args[num_args], XtNx, x);	num_args++;
1250	XtSetArg(args[num_args], XtNy, y);	num_args++;
1251	XtSetValues(ispell.shell, args, num_args);
1252    }
1253
1254    if (ispell.repeat)
1255	IspellSetRepeated(False);
1256    ispell.lock = ispell.repeat = ispell.checkit = False;
1257    ispell.stat = SEND;
1258
1259    IspellSetSensitive(True);
1260    XtSetSensitive(ispell.undo, False);
1261
1262    XtSetArg(args[0], XtNlabel, "");
1263    XtSetValues(ispell.word, args, 1);
1264
1265    XtSetArg(args[0], XtNstring, "");
1266    XtSetValues(ispell.text, args, 1);
1267
1268    XtSetArg(args[0], XtNlist, &strs);
1269    XtSetArg(args[1], XtNnumberStrings, &n_strs);
1270    XtGetValues(ispell.list, args, 2);
1271
1272    list = (char**)XtMalloc(sizeof(char**));
1273    list[0] = XtNewString("");
1274    XtSetArg(args[0], XtNlist, list);
1275    XtSetArg(args[1], XtNnumberStrings, 1);
1276    XtSetValues(ispell.list, args, 2);
1277
1278    if (n_strs > 1 || (XtName(ispell.list) != strs[0])) {
1279	while (--n_strs > -1)
1280	    XtFree(strs[n_strs]);
1281	XtFree((char*)strs);
1282    }
1283
1284    IspellSetStatus(ispell.working_label);
1285
1286    if (!ispell.pid)
1287	(void)IspellStartProcess();
1288    else {
1289	ispell.right = XawTextGetInsertionPoint(ispell.ascii);
1290	ispell.right = XawTextSourceScan(ispell.source, ispell.right,
1291					      XawstEOL, XawsdLeft, 1, True);
1292	while (IspellSend() == 0)
1293	    ;
1294    }
1295
1296    XtPopup(ispell.shell, XtGrabExclusive);
1297    XtSetKeyboardFocus(ispell.shell, ispell.text);
1298}
1299
1300static Bool
1301IspellStartProcess(void)
1302{
1303    if (!ispell.pid) {
1304	int len;
1305	char format[32];
1306	static char *command;
1307
1308	ispell.source = XawTextGetSource(ispell.ascii);
1309
1310	if (command)
1311	    XtFree(command);
1312
1313	strcpy(format, "%s -a");
1314	len = strlen(ispell.cmd) + 4;
1315	if (ispell.dictionary && *ispell.dictionary) {
1316	    len += strlen(ispell.dictionary) + 6;
1317	    strcat(format, " -d '%s'");
1318	    if (ispell.wchars && *ispell.wchars) {
1319		len += strlen(ispell.wchars + 6);
1320		strcat(format, " -w '%s'");
1321	    }
1322	}
1323	command = XtMalloc(len);
1324	XmuSnprintf(command, len, format,
1325		    ispell.cmd, ispell.dictionary, ispell.wchars);
1326
1327	pipe(ispell.ifd);
1328	pipe(ispell.ofd);
1329	if ((ispell.pid = fork()) == 0) {
1330	    close(0);
1331	    close(1);
1332	    dup2(ispell.ofd[0], 0);
1333	    dup2(ispell.ifd[1], 1);
1334	    close(ispell.ofd[0]);
1335	    close(ispell.ofd[1]);
1336	    close(ispell.ifd[0]);
1337	    close(ispell.ifd[1]);
1338	    if (!international)
1339		setlocale(LC_ALL, "ISO-8859-1");
1340	    execl("/bin/sh", "sh", "-c", command, NULL);
1341	    exit(-127);
1342	}
1343	else if (ispell.pid < 0) {
1344	    fprintf(stderr, "Cannot fork\n");
1345	    exit(1);
1346	}
1347	ispell.buf = XtMalloc(ispell.bufsiz = BUFSIZ);
1348	ispell.right = -1;
1349	ispell.id = XtAppAddInput(XtWidgetToApplicationContext(ispell.shell),
1350				  ispell.ifd[0], (XtPointer)XtInputReadMask,
1351				  IspellInputCallback, NULL);
1352	fcntl(ispell.ifd[0], F_SETFL, O_NONBLOCK);
1353    }
1354    else
1355	return (False);
1356
1357    return (True);
1358}
1359
1360/*ARGSUSED*/
1361static void
1362PopdownIspell(Widget w, XtPointer client_data, XtPointer call_data)
1363{
1364    (void)IspellEndProcess((Bool)(long)client_data, True);
1365    XtPopdown(ispell.shell);
1366    *ispell.sentbuf = '\0';
1367}
1368
1369static Bool
1370IspellCheckProcess(void)
1371{
1372    int status;
1373
1374    if (ispell.pid) {
1375	waitpid(ispell.pid, &status, WNOHANG);
1376	if (WIFEXITED(status)) {
1377	    ispell.pid = 0;
1378	}
1379	else
1380	    return (True);
1381    }
1382
1383    return (False);
1384}
1385
1386static Bool
1387IspellEndProcess(Bool killit, Bool killundo)
1388{
1389    ispell.source = NULL;
1390
1391    if (ispell.pid) {
1392	IgnoreEntry	*ientry;
1393	ReplaceEntry	*rentry;
1394
1395	/* insert added words in private dictionary */
1396	for (ientry = (IgnoreEntry *)hash_iter_first(ignore_hash);
1397	     ientry;
1398	     ientry = (IgnoreEntry *)hash_iter_next(ignore_hash)) {
1399	    if (ientry->add) {
1400		if (ientry->add == UNCAP)
1401		    write(ispell.ofd[1], "&", 1);
1402		else
1403		    write(ispell.ofd[1], "*", 1);
1404		write(ispell.ofd[1], ientry->word->value, ientry->word->length);
1405		write(ispell.ofd[1], "\n", 1);
1406	    }
1407	}
1408	write(ispell.ofd[1], "#\n", 2);		/* save dictionary */
1409	hash_clr(ignore_hash);
1410
1411	if (killit) {
1412	    XtRemoveInput(ispell.id);
1413
1414	    close(ispell.ofd[0]);
1415	    close(ispell.ofd[1]);
1416	    close(ispell.ifd[0]);
1417	    close(ispell.ifd[1]);
1418
1419	    /* if something goes wrong, we don't want to block here forever */
1420	    old_timeout = signal(SIGALRM, timeout_signal);
1421	    alarm(10);
1422	    waitpid(ispell.pid, NULL, 0);
1423	    alarm(0);
1424	    signal(SIGALRM, old_timeout);
1425
1426	    ispell.pid = 0;
1427	    if (ispell.buf)
1428		XtFree(ispell.buf);
1429	    ispell.buf = NULL;
1430
1431	    /* forget about replace matches */
1432	    for (rentry = (ReplaceEntry *)hash_iter_first(replace_hash);
1433		 rentry;
1434		 rentry = (ReplaceEntry *)hash_iter_next(replace_hash)) {
1435		XtFree(rentry->replace);
1436	    }
1437	    hash_clr(replace_hash);
1438	}
1439
1440	if (killundo)
1441	    IspellKillUndoBuffer();
1442    }
1443    else
1444	return (False);
1445
1446    return (True);
1447}
1448
1449static void
1450IspellKillUndoBuffer(void)
1451{
1452    ispell_undo *undo, *pundo;
1453
1454    undo = pundo = ispell.undo_base;
1455    while (undo) {
1456	undo = undo->next;
1457	if (pundo->undo_str)
1458	    XtFree(pundo->undo_str);
1459	XtFree((char*)pundo);
1460	pundo = undo;
1461    }
1462    ispell.undo_base = ispell.undo_head = NULL;
1463    ispell.undo_for = NULL;
1464    ispell.undo_depth = 0;
1465    XtSetSensitive(ispell.undo, False);
1466}
1467
1468/*ARGSUSED*/
1469static void
1470RevertIspell(Widget w, XtPointer client_data, XtPointer call_data)
1471{
1472    Arg args[1];
1473    char *string, *repstr = NULL;
1474
1475    XtSetArg(args[0], XtNlabel, &string);
1476    XtGetValues(ispell.word, args, 1);
1477    if ((repstr = strchr(string, ' ')) != NULL) {
1478	string = repstr = XtNewString(string);
1479	*strchr(repstr, ' ') = '\0';
1480    }
1481    XtSetArg(args[0], XtNstring, string);
1482    XtSetValues(ispell.text, args, 1);
1483    if (repstr)
1484	XtFree(repstr);
1485}
1486
1487/*ARGSUSED*/
1488static void
1489SelectIspell(Widget w, XtPointer client_data, XtPointer call_data)
1490{
1491    XawListReturnStruct *info = (XawListReturnStruct *)call_data;
1492    Arg args[1];
1493
1494    XtSetArg(args[0], XtNstring, ispell.item = info->string);
1495    XtSetValues(ispell.text, args, 1);
1496}
1497
1498/*ARGSUSED*/
1499void
1500ReplaceIspell(Widget w, XtPointer client_data, XtPointer call_data)
1501{
1502    XawTextPosition pos = XawTextGetInsertionPoint(ispell.ascii);
1503    XawTextBlock check, search, replace;
1504    Arg args[1];
1505    char *text;
1506
1507    if (!ispell.lock)
1508	return;
1509
1510    XtSetArg(args[0], XtNlabel, &text);
1511    XtGetValues(ispell.word, args, 1);
1512    search.ptr = text;
1513    search.format = XawFmt8Bit;
1514    search.firstPos = 0;
1515    search.length = ispell.right - pos;
1516
1517    XtSetArg(args[0], XtNstring, &text);
1518    XtGetValues(ispell.text, args, 1);
1519    replace.ptr = text;
1520    replace.format = XawFmt8Bit;
1521    replace.firstPos = 0;
1522    replace.length = strlen(text);
1523
1524    if (strcmp(search.ptr, replace.ptr) != 0 &&
1525	XawTextReplace(ispell.ascii, pos, pos + search.length,
1526		       &replace) == XawEditDone) {
1527	ispell.right += replace.length - search.length;
1528	IspellCheckUndo();
1529	ispell.undo_head->undo_str = NULL;
1530	ispell.undo_head->undo_pos = pos;
1531	ispell.undo_head->undo_count = 1;
1532
1533	if (ispell.repeat) {
1534	    ispell.undo_head->repeat = 2; /* To recognize later it was replaced */
1535	    ispell.undo_head->undo_count = ispell.right;
1536	    ispell.undo_head->undo_str = XtNewString(search.ptr);
1537	}
1538	if (client_data && !ispell.repeat) {
1539	    XawTextDisableRedisplay(ispell.ascii);
1540	    pos = ispell.right;
1541	    while ((pos = XawTextSourceSearch(ispell.source, pos, XawsdRight, &search))
1542		!= XawTextSearchError) {
1543		Bool do_replace = True;
1544		char mb[sizeof(wchar_t)];
1545
1546		if (XawTextSourceRead(ispell.source, pos - 1, &check, 1) > 0) {
1547		    if (international)
1548			wctomb(mb, *(wchar_t*)check.ptr);
1549		    else
1550			mb[0] = check.ptr[0];
1551		    do_replace = !isalpha(*mb) && *mb && !strchr(ispell.wchars, *mb);
1552		}
1553		if (do_replace &&
1554		    XawTextSourceRead(ispell.source, pos + search.length, &check, 1) > 0) {
1555		    if (international)
1556			wctomb(mb, *(wchar_t*)check.ptr);
1557		    else
1558			mb[0] = check.ptr[0];
1559		    do_replace = !isalpha(*mb) && *mb && !strchr(ispell.wchars, *mb);
1560		}
1561		if (do_replace) {
1562		    XawTextReplace(ispell.ascii, pos, pos + search.length, &replace);
1563		    ++ispell.undo_head->undo_count;
1564		}
1565		pos += search.length;
1566	    }
1567	    XawTextEnableRedisplay(ispell.ascii);
1568	}
1569	(void)IspellReplacedWord(search.ptr, replace.ptr);
1570
1571	strncpy(&ispell.sentbuf[1], replace.ptr, sizeof(ispell.sentbuf) - 2);
1572	ispell.sentbuf[sizeof(ispell.sentbuf) - 1] = '\0';
1573    }
1574    else
1575	Feep();
1576
1577    if (ispell.repeat)
1578	ispell.right = ispell.left = XawTextGetInsertionPoint(ispell.ascii);
1579    else if (!ispell.terse_mode || !ispell.item ||
1580	     strcmp(ispell.item, replace.ptr))
1581	ispell.right = ispell.left;	/* check it again! */
1582
1583    ispell.lock = ispell.checkit = False;
1584
1585    ispell.stat = SEND;
1586    IspellSetStatus(ispell.working_label);
1587    while (IspellSend() == 0)
1588	;
1589}
1590
1591/*ARGSUSED*/
1592void
1593IgnoreIspell(Widget w, XtPointer client_data, XtPointer call_data)
1594{
1595    Arg args[1];
1596    char *text;
1597
1598    if (!ispell.lock)
1599	return;
1600
1601    XtSetArg(args[0], XtNlabel, &text);
1602    XtGetValues(ispell.word, args, 1);
1603
1604    IspellCheckUndo();
1605
1606    if ((ispell.undo_head->repeat = ispell.repeat) != False) {
1607	ispell.undo_head->undo_count = ispell.right;
1608	ispell.undo_head->undo_str = XtNewString(text);
1609    }
1610    else
1611	ispell.undo_head->undo_count = 0;
1612
1613    ispell.undo_head->undo_pos = XawTextGetInsertionPoint(ispell.ascii);
1614
1615    if (!ispell.repeat) {
1616	if (client_data) {
1617	    IspellIgnoredWord(text, ADD, 0);
1618	    ispell.undo_head->undo_str = XtNewString(text);
1619	}
1620	else
1621	    ispell.undo_head->undo_str = NULL;
1622    }
1623
1624    ispell.lock = ispell.checkit = False;
1625
1626    ispell.stat = SEND;
1627    IspellSetStatus(ispell.working_label);
1628    while (IspellSend() == 0)
1629	;
1630}
1631
1632/*ARGSUSED*/
1633void
1634AddIspell(Widget w, XtPointer client_data, XtPointer call_data)
1635{
1636    Arg args[1];
1637    char *text;
1638    int cmd = (long)client_data;
1639
1640    if (!ispell.lock || ispell.repeat)
1641	return;
1642
1643    XtSetArg(args[0], XtNlabel, &text);
1644    XtGetValues(ispell.word, args, 1);
1645
1646    IspellCheckUndo();
1647    ispell.undo_head->undo_str = XtNewString(text);
1648    ispell.undo_head->undo_pos = XawTextGetInsertionPoint(ispell.ascii);
1649    ispell.undo_head->undo_count = -cmd;
1650
1651    (void)IspellIgnoredWord(text, ADD, cmd);
1652
1653    ispell.lock = ispell.checkit = False;
1654    ispell.stat = SEND;
1655    IspellSetStatus(ispell.working_label);
1656    while (IspellSend() == 0)
1657	;
1658}
1659
1660/*ARGSUSED*/
1661static void
1662UndoIspell(Widget w, XtPointer client_data, XtPointer call_data)
1663{
1664    Bool enable_redisplay = False;
1665    ispell_undo *undo = ispell.undo_head;
1666
1667    if ((!ispell.lock && ispell.stat) || !undo)
1668	return;
1669
1670    if (ispell.undo_for && strcmp(ispell.undo_for, ispell.dictionary)) {
1671	XeditPrintf("Undo: Dictionary changed. Undo information was lost.\n");
1672	IspellKillUndoBuffer();
1673	Feep();
1674	return;
1675    }
1676
1677    if (undo->terse != ispell.terse_mode)
1678	IspellSetTerseMode(undo->terse);
1679
1680    if (undo->format != ispell.format_info->value) {
1681	struct _ispell_format *fmt = &ispell_format[undo->format];
1682	ChangeFormatIspell(fmt->sme, (XtPointer)fmt, NULL);
1683    }
1684
1685    if (undo->undo_count > 0 && !undo->repeat) {
1686	XawTextPosition tmp;
1687
1688	enable_redisplay = undo->undo_count > 1;
1689	if (enable_redisplay)
1690	    XawTextDisableRedisplay(ispell.ascii);
1691	while (undo->undo_count--)
1692	    if (!_XawTextSrcUndo((TextSrcObject)ispell.source, &tmp)) {
1693		Feep();
1694		break;
1695	    }
1696    }
1697    else if (undo->undo_count < 0) {
1698	if (undo->undo_str)
1699	    (void)IspellIgnoredWord(undo->undo_str, REMOVE, -undo->undo_count);
1700    }
1701    else if (undo->undo_str) {
1702	if (!undo->repeat)
1703	    IspellIgnoredWord(undo->undo_str, REMOVE, 0);
1704    }
1705
1706    XawTextSetInsertionPoint(ispell.ascii,
1707			     ispell.right = ispell.left = undo->undo_pos);
1708    if (enable_redisplay)
1709	XawTextEnableRedisplay(ispell.ascii);
1710
1711    /* need to do it because may be two misspelled words together */
1712    if (undo->repeat) {
1713	char **list, **old_list;
1714	int old_len;
1715	Arg args[2];
1716
1717	if (undo->repeat > 1) {
1718	    XawTextDisableRedisplay(ispell.ascii);
1719	    if (!_XawTextSrcUndo((TextSrcObject)ispell.source, &ispell.right))
1720		Feep();
1721	    XawTextEnableRedisplay(ispell.ascii);
1722	}
1723	else
1724	    ispell.right = (XawTextPosition)undo->undo_count;
1725	IspellSetRepeated(ispell.repeat = True);
1726	XtSetArg(args[0], XtNlabel, undo->undo_str);
1727	XtSetValues(ispell.word, args, 1);
1728	XmuSnprintf(ispell.sentbuf, sizeof(ispell.sentbuf), "^%s",
1729		    strrchr(undo->undo_str, ' ') + 1);
1730	strcpy(ispell.sendbuf, ispell.sentbuf);
1731	XtSetArg(args[0], XtNstring, &ispell.sentbuf[1]);
1732	XtSetValues(ispell.text, args, 1);
1733
1734	XtSetArg(args[0], XtNlist, &old_list);
1735	XtSetArg(args[1], XtNnumberStrings, &old_len);
1736	XtGetValues(ispell.list, args, 2);
1737
1738	list = (char **)XtMalloc(sizeof(char*));
1739	list[0] = XtNewString(&ispell.sentbuf[1]);
1740	XtSetArg(args[0], XtNlist, list);
1741	XtSetArg(args[1], XtNnumberStrings, 1);
1742	XtSetValues(ispell.list, args, 2);
1743	XtSetSensitive(ispell.list, True);
1744	XawListHighlight(ispell.list, 0);
1745
1746	if (old_len > 1 || (XtName(ispell.list) != old_list[0])) {
1747	    while (--old_len > -1)
1748		XtFree(old_list[old_len]);
1749	    XtFree((char*)old_list);
1750	}
1751
1752	IspellSetSelection(ispell.left, ispell.right);
1753	IspellSetStatus(ispell.repeat_label);
1754	ispell.lock = True;
1755	ispell.checkit = False;
1756    }
1757    else if (ispell.repeat) {
1758	*ispell.sentbuf = '\0';
1759	IspellSetRepeated(ispell.repeat = False);
1760    }
1761
1762    if (undo->prev)
1763	undo->prev->next = NULL;
1764    ispell.undo_head = undo->prev;
1765    if (undo == ispell.undo_base) {
1766	ispell.undo_base = NULL;
1767	ispell.undo_for = NULL;
1768	XtSetSensitive(ispell.undo, False);
1769    }
1770    if (undo->undo_str)
1771	XtFree(undo->undo_str);
1772    XtFree((char*)undo);
1773    --ispell.undo_depth;
1774
1775    if (!ispell.stat || ispell.checkit)
1776	IspellSetSensitive(True);
1777
1778    if (!ispell.repeat) {
1779	ispell.lock = ispell.checkit = False;
1780	ispell.stat = SEND;
1781	IspellSetStatus(ispell.working_label);
1782	while (IspellSend() == 0)
1783	    ;
1784    }
1785}
1786
1787/*ARGSUSED*/
1788static void
1789CheckIspell(Widget w, XtPointer client_data, XtPointer call_data)
1790{
1791    Arg args[1];
1792    char *text, *str, string[1024];
1793    int i, len;
1794
1795    if (!ispell.lock)
1796	return;
1797
1798    XtSetArg(args[0], XtNstring, &text);
1799    XtGetValues(ispell.text, args, 1);
1800
1801    /* Check only a word at a time */
1802    len = 0;
1803    str = text;
1804    while (*str) {
1805	if (isalpha(*str) || strchr(ispell.wchars, *str))
1806	    break;
1807	++str;
1808	++len;
1809    }
1810    i = 0;
1811    while (*str) {
1812	if (isalpha(*str) || strchr(ispell.wchars, *str))
1813	    string[i++] = *str++;
1814	else
1815	    break;
1816    }
1817    string[i] = '\0';
1818
1819    if (strcmp(text, string)) {
1820	XawTextPosition pos = XawTextGetInsertionPoint(ispell.text) - len;
1821
1822	XtSetArg(args[0], XtNstring, string);
1823	XtSetValues(ispell.text, args, 1);
1824	XawTextSetInsertionPoint(ispell.text, pos);
1825	Feep();
1826    }
1827
1828    if (i == 0) {
1829	Feep();
1830	return;
1831    }
1832
1833    len = XmuSnprintf(ispell.sendbuf, sizeof(ispell.sendbuf), "^%s\n", string);
1834
1835    ispell.sendbuf[sizeof(ispell.sendbuf) - 1] = '\n';
1836
1837    write(ispell.ofd[1], ispell.sendbuf, len);
1838
1839    ispell.lock = False;
1840    ispell.checkit = True;
1841    ispell.stat = RECEIVE;
1842}
1843
1844/*ARGSUSED*/
1845static void
1846LookIspell(Widget w, XtPointer client_data, XtPointer call_data)
1847{
1848    int len, old_len;
1849    FILE *fd;
1850    Arg args[2];
1851    char *text, *str, **list, **old_list, command[1024], buffer[1024];
1852    Bool sensitive = True;
1853
1854    if (!ispell.lock)
1855	return;
1856
1857    XtSetArg(args[0], XtNstring, &text);
1858    XtGetValues(ispell.text, args, 1);
1859
1860    if (!*text) {
1861	Feep();
1862	return;
1863    }
1864
1865    if (strlen(ispell.look_cmd) + strlen(text) + strlen(ispell.words_file) + 8
1866	> sizeof(command) - 1) {
1867	fprintf(stderr, "Command line too large\n");
1868	return;
1869    }
1870
1871    XmuSnprintf(command, sizeof(command), "%s '^%s.*$' %s",
1872		ispell.look_cmd, text, ispell.words_file);
1873
1874    if ((fd = popen(command, "r")) == NULL) {
1875	fprintf(stderr, "Cannot popen '%s'\n", ispell.look_cmd);
1876	return;
1877    }
1878
1879    list = NULL;
1880    len = 0;
1881
1882#define	MAX_LOOK_RESULTS	256
1883    while (fgets(buffer, sizeof(buffer), fd) != NULL) {
1884	if ((str = strchr(buffer, '\n')) == NULL) {
1885	    fprintf(stderr, "String is too large\n");
1886	    break;
1887	}
1888	*str = '\0';
1889	if ((len % 16) == 0)
1890	    list = (char**)XtRealloc((char*)list, sizeof(char*) * (len + 16));
1891	list[len] = XtNewString(buffer);
1892	if (++len >= MAX_LOOK_RESULTS) {
1893	    Feep();
1894	    break;
1895	}
1896    }
1897#undef MAX_LOOK_RESULTS
1898
1899    XtSetArg(args[0], XtNlist, &old_list);
1900    XtSetArg(args[1], XtNnumberStrings, &old_len);
1901    XtGetValues(ispell.list, args, 2);
1902
1903    if (len == 0) {
1904	list = (char**)XtMalloc(sizeof(char*));
1905	list[0] = XtNewString("");
1906	len = 1;
1907	sensitive = False;
1908    }
1909
1910    XtSetArg(args[0], XtNlist, list);
1911    XtSetArg(args[1], XtNnumberStrings, len);
1912    XtSetValues(ispell.list, args, 2);
1913
1914    XtSetSensitive(ispell.list, sensitive);
1915    IspellSetStatus(sensitive ? ispell.look_label : ispell.none_label);
1916
1917    if (old_len > 1 || (XtName(ispell.list) != old_list[0])) {
1918	while (--old_len > -1)
1919	    XtFree(old_list[old_len]);
1920	XtFree((char*)old_list);
1921    }
1922
1923    pclose(fd);
1924}
1925
1926/*ARGSUSED*/
1927static void
1928ToggleTerseIspell(Widget w, XtPointer client_data, XtPointer call_data)
1929{
1930    if (!ispell.lock)
1931	return;
1932
1933    ispell.terse_mode = !ispell.terse_mode;
1934    write(ispell.ofd[1], ispell.terse_mode ? "!\n" : "%\n", 2);
1935}
1936
1937/*ARGSUSED*/
1938static void
1939ChangeDictionaryIspell(Widget w, XtPointer client_data, XtPointer call_data)
1940{
1941    ispell_dict *tmp, *dic = (ispell_dict*)client_data;
1942    XawTextPosition pos = XawTextGetInsertionPoint(ispell.ascii);
1943    XawTextPosition right = ispell.right;
1944    Arg args[1];
1945
1946    if (strcmp(XtName(dic->sme), ispell.dictionary) == 0)
1947	return;
1948
1949    if (!ispell.lock) {
1950	if (IspellCheckProcess()) {
1951	    Feep();
1952	    return;
1953	}
1954    }
1955
1956    for (tmp = ispell.dict_info; tmp; tmp = tmp->next)
1957	if (strcmp(XtName(tmp->sme), ispell.dictionary) == 0) {
1958	    XtSetArg(args[0], XtNleftBitmap, None);
1959	    XtSetValues(tmp->sme, args, 1);
1960	}
1961
1962    if (ispell.undo_base && !ispell.undo_for)
1963	ispell.undo_for = ispell.dictionary;
1964
1965    XtSetArg(args[0], XtNleftBitmap, flist.pixmap);
1966    XtSetValues(dic->sme, args, 1);
1967    ispell.dictionary = XtName(dic->sme);
1968    ispell.wchars = dic->wchars;
1969    XtSetArg(args[0], XtNlabel, XtName(dic->sme));
1970    XtSetValues(ispell.dict, args, 1);
1971
1972    IspellSetStatus(ispell.working_label);
1973
1974    (void)IspellEndProcess(True, False);
1975    ispell.lock = ispell.checkit = False;
1976    (void)IspellStartProcess();
1977
1978    ispell.stat = RECEIVE;
1979
1980    /* restart at the same selected word */
1981    if (ispell.repeat == False)
1982	ispell.left = ispell.right = pos;
1983    else
1984	ispell.right = right;
1985}
1986
1987/*ARGSUSED*/
1988static void
1989ChangeFormatIspell(Widget w, XtPointer client_data, XtPointer call_data)
1990{
1991    struct _ispell_format *fmt = (struct _ispell_format*)client_data;
1992    Arg args[1];
1993
1994    if (strcmp(fmt->name, ispell.formatting) == 0)
1995	return;
1996
1997    if (!ispell.lock) {
1998	Feep();
1999	return;
2000    }
2001
2002    XtSetArg(args[0], XtNleftBitmap, None);
2003    XtSetValues(ispell.format_info->sme, args, 1);
2004
2005    XtSetArg(args[0], XtNleftBitmap, flist.pixmap);
2006    XtSetValues(fmt->sme, args, 1);
2007    ispell.formatting = fmt->name;
2008    ispell.format_info = fmt;
2009    XtSetArg(args[0], XtNlabel, fmt->name);
2010    XtSetValues(ispell.format, args, 1);
2011}
2012
2013static Bool
2014InitIspell(void)
2015{
2016    Atom delete_window;
2017    char *str, *list;
2018    XtResource dict_res;
2019    ispell_dict *dict, *prev_dict;
2020    int i;
2021    static XtResource text_res[] = {
2022	{"skipLines", "Skip", XtRString, sizeof(char*),
2023	 XtOffsetOf(struct _ispell, skip), XtRString, "#"},
2024    };
2025
2026    if (ispell.shell)
2027	return (False);
2028
2029    replace_hash = hash_new(RSTRTBLSZ, NULL);
2030    ignore_hash = hash_new(ISTRTBLSZ, NULL);
2031
2032    ispell.shell	= XtCreatePopupShell("ispell", transientShellWidgetClass,
2033					     topwindow, NULL, 0);
2034
2035    XtGetApplicationResources(ispell.shell, (XtPointer)&ispell, resources,
2036			      XtNumber(resources), NULL, 0);
2037
2038    ispell.form		= XtCreateManagedWidget("form", formWidgetClass,
2039						ispell.shell, NULL, 0);
2040    ispell.mispelled	= XtCreateManagedWidget("mispelled", labelWidgetClass,
2041						ispell.form, NULL, 0);
2042    ispell.repeated	= XtCreateWidget("repeated", labelWidgetClass,
2043					 ispell.form, NULL, 0);
2044    ispell.word		= XtCreateManagedWidget("word", commandWidgetClass,
2045						ispell.form, NULL, 0);
2046    XtAddCallback(ispell.word, XtNcallback, RevertIspell, NULL);
2047    ispell.replacement	= XtCreateManagedWidget("replacement", labelWidgetClass,
2048						ispell.form, NULL, 0);
2049    ispell.text		= XtVaCreateManagedWidget("text", asciiTextWidgetClass,
2050						ispell.form,
2051						XtNeditType, XawtextEdit,
2052						NULL);
2053    ispell.suggestions	= XtCreateManagedWidget("suggestions", labelWidgetClass,
2054						ispell.form, NULL, 0);
2055    ispell.viewport	= XtCreateManagedWidget("viewport", viewportWidgetClass,
2056						ispell.form, NULL, 0);
2057    ispell.list		= XtCreateManagedWidget("list", listWidgetClass,
2058						ispell.viewport, NULL, 0);
2059    XtAddCallback(ispell.list, XtNcallback, SelectIspell, NULL);
2060    ispell.commands	= XtCreateManagedWidget("commands", formWidgetClass,
2061						ispell.form, NULL, 0);
2062    ispell.check	= XtCreateManagedWidget("check", commandWidgetClass,
2063						ispell.commands, NULL, 0);
2064    XtAddCallback(ispell.check, XtNcallback, CheckIspell, NULL);
2065    ispell.look		= XtCreateManagedWidget("look", commandWidgetClass,
2066						ispell.commands, NULL, 0);
2067    XtAddCallback(ispell.look, XtNcallback, LookIspell, NULL);
2068    ispell.undo		= XtCreateManagedWidget("undo", commandWidgetClass,
2069						ispell.commands, NULL, 0);
2070    XtAddCallback(ispell.undo, XtNcallback, UndoIspell, NULL);
2071    ispell.replace	= XtCreateManagedWidget("replace", commandWidgetClass,
2072						ispell.commands, NULL, 0);
2073    XtAddCallback(ispell.replace, XtNcallback, ReplaceIspell, (XtPointer)False);
2074    ispell.replaceAll	= XtCreateManagedWidget("replaceAll", commandWidgetClass,
2075						ispell.commands, NULL, 0);
2076    XtAddCallback(ispell.replaceAll, XtNcallback, ReplaceIspell, (XtPointer)True);
2077    ispell.ignore	= XtCreateManagedWidget("ignore", commandWidgetClass,
2078						ispell.commands, NULL, 0);
2079    XtAddCallback(ispell.ignore, XtNcallback, IgnoreIspell, (XtPointer)False);
2080    ispell.ignoreAll	= XtCreateManagedWidget("ignoreAll", commandWidgetClass,
2081						ispell.commands, NULL, 0);
2082    XtAddCallback(ispell.ignoreAll, XtNcallback, IgnoreIspell, (XtPointer)True);
2083    ispell.add		= XtCreateManagedWidget("add", commandWidgetClass,
2084						ispell.commands, NULL, 0);
2085    XtAddCallback(ispell.add, XtNcallback, AddIspell, (XtPointer)ASIS);
2086    ispell.addUncap	= XtCreateManagedWidget("addUncap", commandWidgetClass,
2087						ispell.commands, NULL, 0);
2088    XtAddCallback(ispell.addUncap, XtNcallback, AddIspell, (XtPointer)UNCAP);
2089    ispell.suspend	= XtCreateManagedWidget("suspend", commandWidgetClass,
2090						ispell.commands, NULL, 0);
2091    XtAddCallback(ispell.suspend, XtNcallback, PopdownIspell, (XtPointer)False);
2092    ispell.cancel	= XtCreateManagedWidget("cancel", commandWidgetClass,
2093						ispell.commands, NULL, 0);
2094    XtAddCallback(ispell.cancel, XtNcallback, PopdownIspell, (XtPointer)True);
2095    ispell.terse	= XtVaCreateManagedWidget("terse", toggleWidgetClass,
2096						  ispell.commands,
2097						  XtNstate, ispell.terse_mode,
2098						  NULL);
2099    XtAddCallback(ispell.terse, XtNcallback, ToggleTerseIspell, NULL);
2100    ispell.status	= XtCreateManagedWidget("status", labelWidgetClass,
2101						ispell.form, NULL, 0);
2102    ispell.options	= XtCreateManagedWidget("options", formWidgetClass,
2103						ispell.form, NULL, 0);
2104    ispell.dict		= XtVaCreateManagedWidget("dict", menuButtonWidgetClass,
2105						  ispell.options,
2106						  XtNmenuName, "dictionaries",
2107						  NULL);
2108    ispell.dictMenu	= XtCreatePopupShell("dictionaries", simpleMenuWidgetClass,
2109					     ispell.options, NULL, 0);
2110    XtRealizeWidget(ispell.dictMenu);
2111
2112    ispell.format	= XtVaCreateManagedWidget("format", menuButtonWidgetClass,
2113						  ispell.options,
2114						  XtNmenuName, "formats",
2115						  NULL);
2116    ispell.formatMenu	= XtCreatePopupShell("formats", simpleMenuWidgetClass,
2117					     ispell.options, NULL, 0);
2118    XtRealizeWidget(ispell.formatMenu);
2119
2120    XtRealizeWidget(ispell.shell);
2121
2122    for (i = 0; i < sizeof(ispell_format) / sizeof(ispell_format[0]); i++) {
2123	struct _ispell_format *fmt = &ispell_format[i];
2124
2125	fmt->sme = XtCreateManagedWidget(fmt->name, smeBSBObjectClass,
2126					 ispell.formatMenu, NULL, 0);
2127	XtAddCallback(fmt->sme, XtNcallback, ChangeFormatIspell, (XtPointer)fmt);
2128
2129	if (strcmp(fmt->name, ispell.formatting) == 0) {
2130	    Arg args[1];
2131
2132	    XtSetArg(args[0], XtNlabel, ispell.formatting);
2133	    XtSetValues(ispell.format, args, 1);
2134	    XtSetArg(args[0], XtNleftBitmap, flist.pixmap);
2135	    XtSetValues(fmt->sme, args, 1);
2136	    ispell.format_info = fmt;
2137	}
2138    }
2139    if (ispell.format_info == NULL) {
2140	Arg args[1];
2141	char msg[256];
2142
2143	ispell.format_info = &ispell_format[TEXT];
2144
2145	XmuSnprintf(msg, sizeof(msg),
2146		    "Unrecognized formatting type \"%s\", will use \"%s\"",
2147		    ispell.formatting, ispell.format_info->name);
2148	XtAppWarning(XtWidgetToApplicationContext(ispell.shell), msg);
2149	ispell.formatting = ispell.format_info->name;
2150
2151	XtSetArg(args[0], XtNlabel, ispell.format_info->name);
2152	XtSetValues(ispell.format, args, 1);
2153	XtSetArg(args[0], XtNleftBitmap, flist.pixmap);
2154	XtSetValues(ispell.format_info->sme, args, 1);
2155    }
2156    XtGetApplicationResources(ispell_format[TEXT].sme, (XtPointer)&ispell,
2157			      text_res, XtNumber(text_res), NULL, 0);
2158
2159    dict_res.resource_name = "wordChars";
2160    dict_res.resource_class = "Chars";
2161    dict_res.resource_type = XtRString;
2162    dict_res.resource_size = sizeof(char*);
2163    dict_res.resource_offset = XtOffsetOf(ispell_dict, wchars);
2164    dict_res.default_type = XtRString;
2165    dict_res.default_addr = "";
2166
2167    list = XtNewString(ispell.dict_list);
2168
2169    /* Create first empty entry */
2170    dict = XtNew(ispell_dict);
2171    dict->sme = XtCreateManagedWidget("", smeBSBObjectClass,
2172				      ispell.dictMenu, NULL, 0);
2173    dict->wchars = "";
2174    XtAddCallback(dict->sme, XtNcallback, ChangeDictionaryIspell,
2175		  (XtPointer)dict);
2176    ispell.dict_info = prev_dict = dict;
2177
2178    for (str = strtok(list, " \t,"); str; str = strtok(NULL, " \t,")) {
2179	dict = XtNew(ispell_dict);
2180	dict->sme = XtCreateManagedWidget(str, smeBSBObjectClass,
2181					  ispell.dictMenu, NULL, 0);
2182	XtGetApplicationResources(dict->sme, (XtPointer)dict, &dict_res,
2183				  1, NULL, 0);
2184	XtAddCallback(dict->sme, XtNcallback, ChangeDictionaryIspell,
2185		      (XtPointer)dict);
2186	prev_dict->next = dict;
2187	prev_dict = dict;
2188	dict->next = NULL;
2189    }
2190    XtFree(list);
2191
2192    for (dict = ispell.dict_info; dict; dict = dict->next) {
2193	if (strcmp(XtName(dict->sme), ispell.dictionary) == 0) {
2194	    Arg args[1];
2195
2196	    XtSetArg(args[0], XtNleftBitmap, flist.pixmap);
2197	    XtSetValues(dict->sme, args, 1);
2198	    XtSetArg(args[0], XtNlabel, XtName(dict->sme));
2199	    XtSetValues(ispell.dict, args, 1);
2200	    ispell.wchars = dict->wchars;
2201	    break;
2202	}
2203    }
2204
2205
2206    delete_window = XInternAtom(XtDisplay(ispell.shell), "WM_DELETE_WINDOW", False);
2207    XSetWMProtocols(XtDisplay(ispell.shell), XtWindow(ispell.shell), &delete_window, 1);
2208
2209    return (True);
2210}
2211