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