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