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