3 * ======================================================
5 * LyX, The Document Processor
7 * Copyright 1995 Matthias Ettrich
8 * Copyright 1995-1998 The LyX Team
10 * ======================================================
21 #include <sys/types.h>
23 #include FORMS_H_LOCATION
25 #if TIME_WITH_SYS_TIME
26 # include <sys/time.h>
30 # include <sys/time.h>
36 #ifdef HAVE_SYS_SELECT_H
37 #include <sys/select.h>
44 #include "spellchecker.h"
48 #include "BufferView.h"
50 #include "lyx_gui_misc.h"
52 #include "support/lstrings.h"
58 // Spellchecker status
68 static bool RunSpellChecker(BufferView * bv);
70 static FILE * in, * out; /* streams to communicate with ispell */
71 pid_t isp_pid = -1; // pid for the `ispell' process. Also used (RO) in
74 // the true spell checker program being used
75 enum ActualSpellChecker {ASC_ISPELL, ASC_ASPELL};
76 static ActualSpellChecker actual_spell_checker;
80 static FD_form_spell_options *fd_form_spell_options = 0;
81 FD_form_spell_check *fd_form_spell_check = 0;
83 //void sigchldhandler(int sig);
84 void sigchldhandler(pid_t pid, int *status);
86 //extern void sigchldchecker(int sig);
87 extern void sigchldchecker(pid_t pid, int *status);
97 misses = static_cast<char**>(0);
105 /***** Spellchecker options *****/
107 // Rewritten to use ordinary LyX xforms loop and OK, Apply and Cancel set,
108 // now also string. Amazing, eh? (Asger)
110 // Set (sane) values in form to current spellchecker options
111 void SpellOptionsUpdate()
113 // Alternate language
114 if (lyxrc.isp_alt_lang.empty()) {
115 lyxrc.isp_use_alt_lang = false;
117 fl_set_input(fd_form_spell_options->altlang_input,
118 lyxrc.isp_alt_lang.c_str());
120 if (lyxrc.isp_use_alt_lang) {
121 fl_set_button(fd_form_spell_options->buflang, 0);
122 fl_set_button(fd_form_spell_options->altlang, 1);
124 fl_set_button(fd_form_spell_options->buflang, 1);
125 fl_set_button(fd_form_spell_options->altlang, 0);
128 // Personal dictionary
129 if (lyxrc.isp_pers_dict.empty()) {
130 lyxrc.isp_use_pers_dict = false;
132 fl_set_input(fd_form_spell_options->perdict_input,
133 lyxrc.isp_pers_dict.c_str());
135 fl_set_button(fd_form_spell_options->perdict,
136 lyxrc.isp_use_pers_dict ? 1:0);
139 if (lyxrc.isp_esc_chars.empty()) {
140 lyxrc.isp_use_esc_chars = false;
142 fl_set_input(fd_form_spell_options->esc_chars_input,
143 lyxrc.isp_esc_chars.c_str());
145 fl_set_button(fd_form_spell_options->esc_chars,
146 lyxrc.isp_use_esc_chars ? 1:0);
149 fl_set_button(fd_form_spell_options->compounds,
150 lyxrc.isp_accept_compound ? 1 : 0);
151 fl_set_button(fd_form_spell_options->inpenc,
152 lyxrc.isp_use_input_encoding ? 1 : 0);
155 // Update spellchecker options
156 void SpellOptionsApplyCB(FL_OBJECT *, long)
158 // Build new status from form data
159 lyxrc.isp_use_alt_lang =
160 fl_get_button(fd_form_spell_options->altlang);
161 lyxrc.isp_use_pers_dict =
162 fl_get_button(fd_form_spell_options->perdict);
163 lyxrc.isp_accept_compound =
164 fl_get_button(fd_form_spell_options->compounds);
165 lyxrc.isp_use_esc_chars =
166 fl_get_button(fd_form_spell_options->esc_chars);
167 lyxrc.isp_use_input_encoding =
168 fl_get_button(fd_form_spell_options->inpenc);
170 // Update strings with data from input fields
172 fl_get_input(fd_form_spell_options->altlang_input);
173 lyxrc.isp_pers_dict =
174 fl_get_input(fd_form_spell_options->perdict_input);
175 lyxrc.isp_esc_chars =
176 fl_get_input(fd_form_spell_options->esc_chars_input);
179 SpellOptionsUpdate();
183 void SpellOptionsCancelCB(FL_OBJECT *, long)
185 fl_hide_form(fd_form_spell_options->form_spell_options);
189 void SpellOptionsOKCB(FL_OBJECT * ob, long data)
191 SpellOptionsApplyCB(ob, data);
192 SpellOptionsCancelCB(ob, data);
196 // Show spellchecker options form
197 void SpellCheckerOptions()
199 // Create form if nescessary
200 if (fd_form_spell_options == 0) {
201 fd_form_spell_options = create_form_form_spell_options();
202 // Make sure pressing the close box does not kill LyX. (RvdK)
203 fl_set_form_atclose(fd_form_spell_options->form_spell_options,
204 CancelCloseBoxCB, 0);
207 // Update form to current options
208 SpellOptionsUpdate();
210 // Focus in alternate language field
211 fl_set_focus_object(fd_form_spell_options->form_spell_options,
212 fd_form_spell_options->altlang_input);
215 if (fd_form_spell_options->form_spell_options->visible) {
216 fl_raise_form(fd_form_spell_options->form_spell_options);
218 fl_show_form(fd_form_spell_options->form_spell_options,
219 FL_PLACE_MOUSE, FL_FULLBORDER,
220 _("Spellchecker Options"));
225 /***** Spellchecker *****/
227 // Could also use a clean up. (Asger Alstrup)
230 void create_ispell_pipe(BufferParams const & params, string const & lang)
232 static char o_buf[BUFSIZ]; // jc: it could be smaller
233 int pipein[2], pipeout[2];
239 if(pipe(pipein) == -1 || pipe(pipeout) == -1) {
240 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
244 if ((out = fdopen(pipein[1], "w")) == 0) {
245 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
250 if ((in = fdopen(pipeout[0], "r")) == 0) {
251 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
256 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
263 lyxerr << "LyX: Can't create child process for spellchecker!"
270 dup2(pipein[0], STDIN_FILENO);
271 dup2(pipeout[1], STDOUT_FILENO);
278 char * tmp = new char[lyxrc.isp_command.length() + 1];
279 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
280 tmp[lyxrc.isp_command.length()] = '\0';
283 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
286 if (lang != "default") {
288 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
290 tmp = new char[lang.length() + 1];
291 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
295 if (lyxrc.isp_accept_compound) {
296 // Consider run-together words as legal compounds
298 string("-C").copy(tmp, 2); tmp[2] = '\0';
301 // Report run-together words with
302 // missing blanks as errors
304 string("-B").copy(tmp, 2); tmp[2] = '\0';
307 if (lyxrc.isp_use_esc_chars) {
308 // Specify additional characters that
309 // can be part of a word
311 string("-w").copy(tmp, 2); tmp[2] = '\0';
313 // Put the escape chars in ""s
314 string tms = "\"" + lyxrc.isp_esc_chars + "\"";
315 tmp = new char[tms.length() + 1];
316 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
319 if (lyxrc.isp_use_pers_dict) {
320 // Specify an alternate personal dictionary
322 string("-p").copy(tmp, 2);
325 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
326 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
327 tmp[lyxrc.isp_pers_dict.length()] = '\0';
330 if (lyxrc.isp_use_input_encoding &&
331 params.inputenc != "default") {
332 string enc = (params.inputenc == "auto")
333 ? params.language_info->encoding()->LatexName()
335 string::size_type n = enc.length();
337 string("-T").copy(tmp, 2); tmp[2] = '\0';
338 argv[argc++] = tmp; // Input encoding
339 tmp = new char[n + 1];
347 execvp(argv[0], const_cast<char * const *>(argv));
349 // free the memory used by string::copy in the
351 for (int i= 0; i < argc -1; ++i)
354 lyxerr << "LyX: Failed to start ispell!" << endl;
358 /* Parent process: Read ispells identification message */
359 // Hmm...what are we using this id msg for? Nothing? (Lgb)
360 // Actually I used it to tell if it's truly Ispell or if it's
361 // aspell -- (kevinatk@home.com)
367 FD_SET(pipeout[0], &infds);
368 tv.tv_sec = 15; // fifteen second timeout. Probably too much,
369 // but it can't really hurt.
372 // Configure provides us with macros which are supposed to do
373 // the right typecast.
374 retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
375 SELECT_TYPE_ARG234 (&infds),
378 SELECT_TYPE_ARG5 (&tv));
381 // Ok, do the reading. We don't have to FD_ISSET since
382 // there is only one fd in infds.
383 fgets(buf, 2048, in);
385 // determine if the spell checker is really Aspell
386 if (strstr(buf, "Aspell"))
387 actual_spell_checker = ASC_ASPELL;
389 actual_spell_checker = ASC_ISPELL;
391 } else if (retval == 0) {
392 // timeout. Give nice message to user.
393 lyxerr << "Ispell read timed out, what now?" << endl;
394 // This probably works but could need some thought
396 close(pipeout[0]); close(pipeout[1]);
397 close(pipein[0]); close(pipein[1]);
400 // Select returned error
401 lyxerr << "Select on ispell returned error, what now?" << endl;
406 // Send word to ispell and get reply
408 isp_result *ispell_check_word(char *word)
410 //Please rewrite to use string.
418 fgets(buf, 1024, in);
420 /* I think we have to check if ispell is still alive here because
421 the signal-handler could have disabled blocking on the fd */
422 if (isp_pid == -1) return 0;
424 result = new isp_result;
427 case '*': // Word found
428 result->flag = ISP_OK;
430 case '+': // Word found through affix removal
431 result->flag = ISP_ROOT;
433 case '-': // Word found through compound formation
434 result->flag = ISP_COMPOUNDWORD;
436 case '\n': // Number or when in terse mode: no problems
437 result->flag = ISP_IGNORE;
439 case '#': // Not found, no near misses and guesses
440 result->flag = ISP_UNKNOWN;
442 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
443 case '&': // Not found, but we have near misses
445 result->flag = ISP_MISSED;
447 // nb is leaked! where should it be freed? I have to
448 // admit I do not understand the intent of the code :(
450 char * nb = new char[result->str.length() + 1];
451 result->str.copy(nb, result->str.length());
452 nb[result->str.length()]= '\0';
453 p = strpbrk(nb+2, " ");
454 sscanf(p, "%d", &count); // Get near misses count
455 result->count = count;
456 if (count) result->misses = new char*[count];
457 p = strpbrk(nb, ":");
459 for (i = 0; i < count; ++i) {
460 result->misses[i] = p;
461 p = strpbrk(p, ",\n");
467 default: // This shouldn't happend, but you know Murphy
468 result->flag = ISP_UNKNOWN;
472 if (result->flag!= ISP_IGNORE) {
473 while (*buf!= '\n') fgets(buf, 255, in); /* wait for ispell to finish */
480 void ispell_terminate()
482 // Note: If you decide to optimize this out when it is not
483 // needed please note that when Aspell is used this command
484 // is also needed to save the replacement dictionary.
485 // -- Kevin Atkinson (kevinatk@home.com)
487 fputs("#\n", out); // Save personal dictionary
495 void ispell_terse_mode()
497 fputs("!\n", out); // Set terse mode (silently accept correct words)
502 void ispell_insert_word(char const *word)
504 fputc('*', out); // Insert word in personal dictionary
511 void ispell_accept_word(char const *word)
513 fputc('@', out); // Accept in this session
519 void ispell_store_replacement(char const *mis, string const & cor) {
520 if(actual_spell_checker == ASC_ASPELL) {
524 fputs(cor.c_str(), out);
530 void ShowSpellChecker(BufferView * bv)
535 // Exit if we don't have a document open
536 if (!bv->available())
539 if (fd_form_spell_check == 0) {
540 fd_form_spell_check = create_form_form_spell_check();
541 // Make sure pressing the close box does not kill LyX. (RvdK)
542 fl_set_form_atclose(fd_form_spell_check->form_spell_check, IgnoreCloseBoxCB, 0);
546 fl_set_slider_value(fd_form_spell_check->slider, 0);
547 fl_set_slider_bounds(fd_form_spell_check->slider, 0, 100);
548 fl_set_object_label(fd_form_spell_check->text, "");
549 fl_set_input(fd_form_spell_check->input, "");
550 fl_clear_browser(fd_form_spell_check->browser);
553 if (fd_form_spell_check->form_spell_check->visible) {
554 fl_raise_form(fd_form_spell_check->form_spell_check);
556 fl_show_form(fd_form_spell_check->form_spell_check,
557 FL_PLACE_MOUSE, FL_FULLBORDER,
560 fl_deactivate_object(fd_form_spell_check->slider);
562 // deactivate insert, accept, replace, and stop
563 fl_deactivate_object(fd_form_spell_check->insert);
564 fl_deactivate_object(fd_form_spell_check->accept);
565 fl_deactivate_object(fd_form_spell_check->ignore);
566 fl_deactivate_object(fd_form_spell_check->replace);
567 fl_deactivate_object(fd_form_spell_check->stop);
568 fl_deactivate_object(fd_form_spell_check->input);
569 fl_deactivate_object(fd_form_spell_check->browser);
570 fl_set_object_lcol(fd_form_spell_check->insert, FL_INACTIVE);
571 fl_set_object_lcol(fd_form_spell_check->accept, FL_INACTIVE);
572 fl_set_object_lcol(fd_form_spell_check->ignore, FL_INACTIVE);
573 fl_set_object_lcol(fd_form_spell_check->replace, FL_INACTIVE);
574 fl_set_object_lcol(fd_form_spell_check->stop, FL_INACTIVE);
575 fl_set_object_lcol(fd_form_spell_check->input, FL_INACTIVE);
576 fl_set_object_lcol(fd_form_spell_check->browser, FL_INACTIVE);
580 if (obj == fd_form_spell_check->options){
581 SpellCheckerOptions();
583 if (obj == fd_form_spell_check->start){
584 // activate insert, accept, and stop
585 fl_activate_object(fd_form_spell_check->insert);
586 fl_activate_object(fd_form_spell_check->accept);
587 fl_activate_object(fd_form_spell_check->ignore);
588 fl_activate_object(fd_form_spell_check->stop);
589 fl_activate_object(fd_form_spell_check->input);
590 fl_activate_object(fd_form_spell_check->browser);
591 fl_set_object_lcol(fd_form_spell_check->insert, FL_BLACK);
592 fl_set_object_lcol(fd_form_spell_check->accept, FL_BLACK);
593 fl_set_object_lcol(fd_form_spell_check->ignore, FL_BLACK);
594 fl_set_object_lcol(fd_form_spell_check->stop, FL_BLACK);
595 fl_set_object_lcol(fd_form_spell_check->input, FL_BLACK);
596 fl_set_object_lcol(fd_form_spell_check->browser, FL_BLACK);
597 // activate replace only if the file is not read-only
598 if (!bv->buffer()->isReadonly()) {
599 fl_activate_object(fd_form_spell_check->replace);
600 fl_set_object_lcol(fd_form_spell_check->replace, FL_BLACK);
603 // deactivate options and start
604 fl_deactivate_object(fd_form_spell_check->options);
605 fl_deactivate_object(fd_form_spell_check->start);
606 fl_set_object_lcol(fd_form_spell_check->options, FL_INACTIVE);
607 fl_set_object_lcol(fd_form_spell_check->start, FL_INACTIVE);
609 ret = RunSpellChecker(bv);
611 // deactivate insert, accept, replace, and stop
612 fl_deactivate_object(fd_form_spell_check->insert);
613 fl_deactivate_object(fd_form_spell_check->accept);
614 fl_deactivate_object(fd_form_spell_check->ignore);
615 fl_deactivate_object(fd_form_spell_check->replace);
616 fl_deactivate_object(fd_form_spell_check->stop);
617 fl_deactivate_object(fd_form_spell_check->input);
618 fl_deactivate_object(fd_form_spell_check->browser);
619 fl_set_object_lcol(fd_form_spell_check->insert, FL_INACTIVE);
620 fl_set_object_lcol(fd_form_spell_check->accept, FL_INACTIVE);
621 fl_set_object_lcol(fd_form_spell_check->ignore, FL_INACTIVE);
622 fl_set_object_lcol(fd_form_spell_check->replace, FL_INACTIVE);
623 fl_set_object_lcol(fd_form_spell_check->stop, FL_INACTIVE);
624 fl_set_object_lcol(fd_form_spell_check->input, FL_INACTIVE);
625 fl_set_object_lcol(fd_form_spell_check->browser, FL_INACTIVE);
627 // activate options and start
628 fl_activate_object(fd_form_spell_check->options);
629 fl_activate_object(fd_form_spell_check->start);
630 fl_set_object_lcol(fd_form_spell_check->options, FL_BLACK);
631 fl_set_object_lcol(fd_form_spell_check->start, FL_BLACK);
633 // if RunSpellChecker returns false quit spellchecker
636 if (obj == fd_form_spell_check->done) break;
638 fl_hide_form(fd_form_spell_check->form_spell_check);
639 bv->endOfSpellCheck();
644 // Perform an ispell session
646 bool RunSpellChecker(BufferView * bv)
652 string tmp = (lyxrc.isp_use_alt_lang) ? lyxrc.isp_alt_lang : bv->buffer()->GetLanguage();
653 bool rtl = tmp == "hebrew" || tmp == "arabic";
655 int oldval = 0; /* used for updating slider only when needed */
658 /* create ispell process */
659 create_ispell_pipe(bv->buffer()->params, tmp);
664 "The ispell-process has died for some reason. *One* possible reason\n"
665 "could be that you do not have a dictionary file\n"
666 "for the language of this document installed.\n"
667 "Check /usr/lib/ispell or set another\n"
668 "dictionary in the Spellchecker Options menu."), "", "");
673 // Put ispell in terse mode to improve speed
676 unsigned int word_count = 0;
678 char * word = bv->nextWord(newval);
679 if (word == 0) break;
682 // Update slider if and only if value has changed
683 newvalue = int(100.0*newval);
684 if(newvalue!= oldval) {
686 fl_set_slider_value(fd_form_spell_check->slider, oldval);
689 if (word_count%1000 == 0) {
690 obj = fl_check_forms();
691 if (obj == fd_form_spell_check->stop) {
696 if (obj == fd_form_spell_check->done) {
703 result = ispell_check_word(word);
710 switch (result->flag) {
714 bv->selectLastWord();
717 reverse(tmp.begin(),tmp.end());
718 fl_set_object_label(fd_form_spell_check->text, tmp.c_str());
720 fl_set_object_label(fd_form_spell_check->text, word);
721 fl_set_input(fd_form_spell_check->input, word);
722 fl_clear_browser(fd_form_spell_check->browser);
723 for (i = 0; i < result->count; ++i) {
725 string tmp = result->misses[i];
726 reverse(tmp.begin(),tmp.end());
727 fl_add_browser_line(fd_form_spell_check->browser, tmp.c_str());
729 fl_add_browser_line(fd_form_spell_check->browser, result->misses[i]);
735 if (obj == fd_form_spell_check->insert) {
736 ispell_insert_word(word);
739 if (obj == fd_form_spell_check->accept) {
740 ispell_accept_word(word);
743 if (obj == fd_form_spell_check->ignore) {
746 if (obj == fd_form_spell_check->replace ||
747 obj == fd_form_spell_check->input) {
748 ispell_store_replacement(word, fl_get_input(fd_form_spell_check->input));
749 bv->replaceWord(fl_get_input(fd_form_spell_check->input));
752 if (obj == fd_form_spell_check->browser) {
753 // implements double click in the browser window.
754 // sent to lyx@via by Mark Burton <mark@cbl.leeds.ac.uk>
756 fl_get_browser(fd_form_spell_check->browser)) {
757 ispell_store_replacement(word, fl_get_input(fd_form_spell_check->input));
758 bv->replaceWord(fl_get_input(fd_form_spell_check->input));
761 clickline = fl_get_browser(fd_form_spell_check->browser);
763 /// fl_set_input(fd_form_spell_check->input, result->misses[clickline-1]); ?
765 string tmp = fl_get_browser_line(fd_form_spell_check->browser,
767 reverse(tmp.begin(),tmp.end());
768 fl_set_input(fd_form_spell_check->input, tmp.c_str());
770 fl_set_input(fd_form_spell_check->input,
771 fl_get_browser_line(fd_form_spell_check->browser,
774 if (obj == fd_form_spell_check->stop) {
781 if (obj == fd_form_spell_check->done) {
797 string word_msg(tostr(word_count));
798 if (word_count != 1) {
799 word_msg += _(" words checked.");
801 word_msg += _(" word checked.");
803 fl_show_message("", _("Spellchecking completed!"),
807 fl_show_message(_("The ispell-process has died for some reason.\n"
808 "Maybe it has been killed."), "", "");
815 void sigchldhandler(pid_t pid, int * status)
818 if (pid == isp_pid) {
820 fcntl(isp_fd, F_SETFL, O_NONBLOCK); /* set the file descriptor
821 to nonblocking so we can
824 sigchldchecker(pid, status);