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