]> git.lyx.org Git - lyx.git/blob - src/sp_spell.C
redraw fix 1.
[lyx.git] / src / sp_spell.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 2001 The LyX Team.
7  *
8  * ======================================================
9  *
10  * \file sp_pspell.C
11  * \author Kevin Atkinson
12  */
13
14 #include <config.h>
15
16 #ifdef __GNUG__
17 #pragma implementation
18 #endif
19
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <cstdio>
25
26 #if TIME_WITH_SYS_TIME
27 # include <sys/time.h>
28 # include <ctime>
29 #else
30 # if HAVE_SYS_TIME_H
31 #  include <sys/time.h>
32 # else
33 #  include <ctime>
34 # endif
35 #endif
36
37 #ifdef HAVE_SYS_SELECT_H
38 # ifdef HAVE_STRINGS_H
39    // <strings.h> is needed at least on AIX because FD_ZERO uses bzero().
40    // BUT we cannot include both string.h and strings.h on Irix 6.5 :(
41 #  ifdef _AIX
42 #   include <strings.h>
43 #  endif
44 # endif
45 #include <sys/select.h>
46 #endif
47
48 #include "LString.h"
49 #include "support/lstrings.h"
50 #include "lyxrc.h"
51 #include "language.h"
52 #include "debug.h"
53 #include "encoding.h"
54 #include "sp_ispell.h"
55
56 #ifndef CXX_GLOBAL_CSTD
57 using std::strcpy;
58 using std::strlen;
59 using std::strpbrk;
60 using std::strstr;
61 #endif
62
63 using std::endl;
64
65 namespace {
66         /// pid for the `ispell' process.
67         pid_t isp_pid = -1;
68 }
69
70 ///
71 // ------------------- start special pspell code/class --------------------
72 ///
73 #ifdef USE_PSPELL
74
75 #include "support/LAssert.h"
76
77 #define USE_ORIGINAL_MANAGER_FUNCS 1
78 # include <pspell/pspell.h>
79
80 #include "sp_pspell.h"
81
82
83 PSpell::PSpell()
84         : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
85           alive_(false)
86 {
87 }
88
89
90 PSpell::PSpell(BufferParams const & params, string const & lang)
91         : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
92           alive_(false)
93 {
94         initialize(params, lang);
95 }
96
97
98 PSpell::~PSpell()
99 {
100         cleanUp();
101         close();
102         if (els)
103                 delete_pspell_string_emulation(els);
104 }
105
106
107 void PSpell::initialize(BufferParams const &, string const & lang)
108 {
109         PspellConfig * config = new_pspell_config();
110         config->replace("language-tag", lang.c_str());
111         spell_error_object = new_pspell_manager(config);
112         if (pspell_error_number(spell_error_object) != 0) {
113                 error_ = pspell_error_message(spell_error_object);
114         } else {
115                 error_ = 0;
116                 sc = to_pspell_manager(spell_error_object);
117                 spell_error_object = 0;
118                 alive_ = true;
119         }
120 }
121
122
123 void PSpell::cleanUp()
124 {
125         if (spell_error_object) {
126                 delete_pspell_can_have_error(spell_error_object);
127                 spell_error_object = 0;
128         }
129 }
130
131
132 enum PSpell::spellStatus PSpell::check(string const & word)
133 {
134         if (!sc)
135                 return flag;
136
137         int word_ok = pspell_manager_check(sc, word.c_str());
138         lyx::Assert(word_ok != -1);
139
140         if (word_ok) {
141                 flag = ISP_OK;
142         } else {
143                 PspellWordList const * sugs =
144                         pspell_manager_suggest(sc, word.c_str());
145                 lyx::Assert(sugs != 0);
146                 els = pspell_word_list_elements(sugs);
147                 if (pspell_word_list_empty(sugs))
148                         flag = ISP_UNKNOWN;
149                 else
150                         flag = ISP_MISSED;
151         }
152         return flag;
153 }
154
155
156 void PSpell::close()
157 {
158         if (sc)
159                 pspell_manager_save_all_word_lists(sc);
160 }
161
162
163 void PSpell::insert(string const & word)
164 {
165         if (sc)
166                 pspell_manager_add_to_personal(sc, word.c_str());
167 }
168
169
170 void PSpell::accept(string const & word)
171 {
172         if (sc)
173                 pspell_manager_add_to_session(sc, word.c_str());
174 }
175
176
177 void PSpell::store(string const & mis, string const & cor)
178 {
179         if (sc)
180                 pspell_manager_store_replacement(sc, mis.c_str(), cor.c_str());
181 }
182
183
184 char const * PSpell::nextMiss()
185 {
186         if (els)
187                 return pspell_string_emulation_next(els);
188         return "";
189 }
190
191
192 char const * PSpell::error()
193 {
194         return error_;
195 }
196
197
198 #endif
199
200 ///
201 // ------------------- start special ispell code/class --------------------
202 ///
203
204 ISpell::ISpell()
205         : str(0), flag(ISP_UNKNOWN)
206 {}
207
208
209 ISpell::ISpell(BufferParams const & params, string const & lang)
210         : str(0), flag(ISP_UNKNOWN)
211 {
212         initialize(params, lang);
213 }
214
215
216 ISpell::~ISpell()
217 {
218         delete[] str;
219 }
220
221
222 char const * ISpell::nextMiss()
223 {
224         if (str == 0 || *(e+1) == '\0') return 0;
225         b = e + 2;
226         e = strpbrk(b, ",\n");
227         *e = '\0';
228         return b;
229 }
230
231
232 void ISpell::initialize(BufferParams const & params, string const & lang)
233 {
234         static char o_buf[BUFSIZ];  // jc: it could be smaller
235         int pipein[2];
236         int pipeout[2];
237         char * argv[14];
238         int argc;
239
240         isp_pid = -1;
241
242         if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
243                 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
244                 goto END;
245         }
246
247         if ((out = fdopen(pipein[1], "w")) == 0) {
248                 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
249                        << endl;
250                 goto END;
251         }
252
253         if ((in = fdopen(pipeout[0], "r")) == 0) {
254                 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
255                        << endl;
256                 goto END;
257         }
258
259         setvbuf(out, o_buf, _IOLBF, BUFSIZ);
260
261         isp_fd = pipeout[0];
262
263         isp_pid = fork();
264
265         if (isp_pid == -1) {
266                 lyxerr << "LyX: Can't create child process for spellchecker!"
267                        << endl;
268                 goto END;
269         }
270
271         if (isp_pid == 0) {
272                 /* child process */
273                 dup2(pipein[0], STDIN_FILENO);
274                 dup2(pipeout[1], STDOUT_FILENO);
275                 ::close(pipein[0]);
276                 ::close(pipein[1]);
277                 ::close(pipeout[0]);
278                 ::close(pipeout[1]);
279
280                 argc = 0;
281                 char * tmp = new char[lyxrc.isp_command.length() + 1];
282                 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
283                 tmp[lyxrc.isp_command.length()] = '\0';
284                 argv[argc++] = tmp;
285                 tmp = new char[3];
286                 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
287                 argv[argc++] = tmp;
288
289                 if (lang != "default") {
290                         tmp = new char[3];
291                         string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
292                         argv[argc++] = tmp;
293                         tmp = new char[lang.length() + 1];
294                         lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
295                         argv[argc++] = tmp;
296                 }
297
298                 if (lyxrc.isp_accept_compound) {
299                         // Consider run-together words as legal compounds
300                         tmp = new char[3];
301                         string("-C").copy(tmp, 2); tmp[2] = '\0';
302                         argv[argc++] = tmp;
303                 } else {
304                         // Report run-together words with
305                         // missing blanks as errors
306                         tmp = new char[3];
307                         string("-B").copy(tmp, 2); tmp[2] = '\0';
308                         argv[argc++] = tmp;
309                 }
310                 if (lyxrc.isp_use_esc_chars) {
311                         // Specify additional characters that
312                         // can be part of a word
313                         tmp = new char[3];
314                         string("-w").copy(tmp, 2); tmp[2] = '\0';
315                         argv[argc++] = tmp;
316                         // Put the escape chars in ""s
317                         string tms = "\"" + lyxrc.isp_esc_chars + "\"";
318                         tmp = new char[tms.length() + 1];
319                         tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
320                         argv[argc++] = tmp;
321                 }
322                 if (lyxrc.isp_use_pers_dict) {
323                         // Specify an alternate personal dictionary
324                         tmp = new char[3];
325                         string("-p").copy(tmp, 2);
326                         tmp[2]= '\0';
327                         argv[argc++] = tmp;
328                         tmp = new char[lyxrc.isp_pers_dict.length() + 1];
329                         lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
330                         tmp[lyxrc.isp_pers_dict.length()] = '\0';
331                         argv[argc++] = tmp;
332                 }
333                 if (lyxrc.isp_use_input_encoding &&
334                     params.inputenc != "default") {
335                         string enc = (params.inputenc == "auto")
336                                 ? params.language->encoding()->LatexName()
337                                 : params.inputenc;
338                         string::size_type n = enc.length();
339                         tmp = new char[3];
340                         string("-T").copy(tmp, 2);
341                         tmp[2] = '\0';
342                         argv[argc++] = tmp; // Input encoding
343                         tmp = new char[n + 1];
344                         enc.copy(tmp, n);
345                         tmp[n] = '\0';
346                         argv[argc++] = tmp;
347                 }
348
349                 argv[argc++] = 0;
350
351                 execvp(argv[0], const_cast<char * const *>(argv));
352
353                 // free the memory used by string::copy in the
354                 // setup of argv
355                 for (int i = 0; i < argc - 1; ++i)
356                         delete[] argv[i];
357
358                 lyxerr << "LyX: Failed to start ispell!" << endl;
359                 _exit(0);
360         }
361         {
362                 /* Parent process: Read ispells identification message */
363                 // Hmm...what are we using this id msg for? Nothing? (Lgb)
364                 // Actually I used it to tell if it's truly Ispell or if it's
365                 // aspell -- (kevinatk@home.com)
366                 char buf[2048];
367                 fd_set infds;
368                 struct timeval tv;
369                 int retval = 0;
370                 FD_ZERO(&infds);
371                 FD_SET(pipeout[0], &infds);
372                 tv.tv_sec = 15; // fifteen second timeout. Probably too much,
373                 // but it can't really hurt.
374                 tv.tv_usec = 0;
375
376                 // Configure provides us with macros which are supposed to do
377                 // the right typecast.
378                 retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
379                                 SELECT_TYPE_ARG234 (&infds),
380                                 0,
381                                 0,
382                                 SELECT_TYPE_ARG5 (&tv));
383
384                 if (retval > 0) {
385                         // Ok, do the reading. We don't have to FD_ISSET since
386                         // there is only one fd in infds.
387                         fgets(buf, 2048, in);
388
389                         // determine if the spell checker is really Aspell
390                         if (strstr(buf, "Aspell"))
391                                 actual_spell_checker = ASC_ASPELL;
392                         else
393                                 actual_spell_checker = ASC_ISPELL;
394
395                         fputs("!\n", out); // Set terse mode (silently accept correct words)
396
397
398                 } else if (retval == 0) {
399                         // timeout. Give nice message to user.
400                         lyxerr << "Ispell read timed out, what now?" << endl;
401                         // This probably works but could need some thought
402                         isp_pid = -1;
403                         ::close(pipeout[0]);
404                         ::close(pipeout[1]);
405                         ::close(pipein[0]);
406                         ::close(pipein[1]);
407                         isp_fd = -1;
408                 } else {
409                         // Select returned error
410                         lyxerr << "Select on ispell returned error, what now?" << endl;
411                 }
412         }
413   END:
414         if (isp_pid == -1) {
415                 error_ =
416                         "\n\n"
417                         "The spellcheck-process has died for some reason.\n"
418                         "*One* possible reason could be that you do not have\n"
419                         "a dictionary file for the language of this document\n"
420                         "installed.\n"
421                         "Check your spellchecker or set another dictionary\n"
422                         "in the Spellchecker Options menu.\n\n";
423         } else {
424                 error_ = 0;
425         }
426 }
427
428
429 /* FIXME: this is a minimalist solution until the above
430  * code is able to work with forkedcall.h. We only need
431  * to reap the zombies here.
432  */
433 void reapSpellchecker(void)
434 {
435         if (isp_pid == -1)
436                 return;
437
438         waitpid(isp_pid, 0, WNOHANG);
439 }
440
441
442 bool ISpell::alive()
443 {
444         return isp_pid != -1;
445 }
446
447
448 void ISpell::cleanUp()
449 {
450         ::fclose(out);
451 }
452
453
454 enum ISpell::spellStatus ISpell::check(string const & word)
455 {
456         //Please rewrite to use string.
457
458         ::fputs(word.c_str(), out);
459         ::fputc('\n', out);
460
461         char buf[1024];
462         ::fgets(buf, 1024, in);
463
464         // I think we have to check if ispell is still alive here because
465         // the signal-handler could have disabled blocking on the fd
466         if (!alive()) return ISP_UNKNOWN;
467
468         switch (*buf) {
469         case '*': // Word found
470                 flag = ISP_OK;
471                 break;
472         case '+': // Word found through affix removal
473                 flag = ISP_ROOT;
474                 break;
475         case '-': // Word found through compound formation
476                 flag = ISP_COMPOUNDWORD;
477                 break;
478         case '\n': // Number or when in terse mode: no problems
479                 flag = ISP_IGNORE;
480                 break;
481         case '#': // Not found, no near misses and guesses
482                 flag = ISP_UNKNOWN;
483                 break;
484         case '?': // Not found, and no near misses, but guesses (guesses are ignored)
485         case '&': // Not found, but we have near misses
486         {
487                 flag = ISP_MISSED;
488                 char * p = strpbrk(buf, ":");
489                 str = new char[strlen(p) + 1];
490                 e   = str;
491                 strcpy(str, p);
492                 break;
493         }
494         default: // This shouldn't happend, but you know Murphy
495                 flag = ISP_UNKNOWN;
496         }
497
498         *buf = 0;
499         if (flag!= ISP_IGNORE) {
500                 while (*buf!= '\n') fgets(buf, 255, in); /* wait for ispell to finish */
501         }
502         return flag;
503 }
504
505
506 void ISpell::close()
507 {
508         // Note: If you decide to optimize this out when it is not
509         // needed please note that when Aspell is used this command
510         // is also needed to save the replacement dictionary.
511         // -- Kevin Atkinson (kevinatk@home.com)
512
513         fputs("#\n", out); // Save personal dictionary
514
515         fflush(out);
516         fclose(out);
517 }
518
519
520 void ISpell::insert(string const & word)
521 {
522         ::fputc('*', out); // Insert word in personal dictionary
523         ::fputs(word.c_str(), out);
524         ::fputc('\n', out);
525 }
526
527
528 void ISpell::accept(string const & word)
529 {
530         ::fputc('@', out); // Accept in this session
531         ::fputs(word.c_str(), out);
532         ::fputc('\n', out);
533 }
534
535
536 void ISpell::store(string const & mis, string const & cor)
537 {
538         if (actual_spell_checker == ASC_ASPELL) {
539                 ::fputs("$$ra ", out);
540                 ::fputs(mis.c_str(), out);
541                 ::fputc(',', out);
542                 ::fputs(cor.c_str(), out);
543                 ::fputc('\n', out);
544         }
545 }
546
547
548 char const * ISpell::error()
549 {
550         return error_;
551 }