]> git.lyx.org Git - lyx.git/blob - src/ispell.C
fix arabtex-related problems (bug 1225 and bug 1404)
[lyx.git] / src / ispell.C
1 /**
2  * \file ispell.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author unknown
7  * \author Angus Leeming
8  * \author John Levon
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "ispell.h"
16
17 #include "bufferparams.h"
18 #include "debug.h"
19 #include "encoding.h"
20 #include "gettext.h"
21 #include "language.h"
22 #include "lyxrc.h"
23 #include "WordLangTuple.h"
24
25 #include "support/forkedcall.h"
26
27 // HP-UX 11.x doesn't have this header
28 #ifdef HAVE_SYS_SELECT_H
29 #include <sys/select.h>
30 #endif
31 #include <sys/time.h>
32
33 using boost::shared_ptr;
34
35 #ifndef CXX_GLOBAL_CSTD
36 using std::strcpy;
37 using std::strlen;
38 using std::strpbrk;
39 #endif
40
41 using std::endl;
42 using std::max;
43 using std::string;
44
45
46 namespace {
47
48 class LaunchIspell : public lyx::support::ForkedProcess {
49         typedef lyx::support::ForkedProcess ForkedProcess;
50 public:
51         ///
52         LaunchIspell(BufferParams const & p, string const & l,
53                      int * in, int * out, int * err)
54                 : params(p), lang(l), pipein(in), pipeout(out), pipeerr(err) {}
55         ///
56         virtual shared_ptr<ForkedProcess> clone() const {
57                 return shared_ptr<ForkedProcess>(new LaunchIspell(*this));
58         }
59         ///
60         int start();
61 private:
62         ///
63         virtual int generateChild();
64
65         ///
66         BufferParams const & params;
67         string const & lang;
68         int * const pipein;
69         int * const pipeout;
70         int * const pipeerr;
71 };
72
73
74 int LaunchIspell::start()
75 {
76         command_ = lyxrc.isp_command;
77         return run(DontWait);
78 }
79
80
81 int LaunchIspell::generateChild()
82 {
83         pid_t isp_pid = fork();
84
85         if (isp_pid != 0) {
86                 // failed (-1) or parent process (>0)
87                 return isp_pid;
88         }
89
90         // child process
91         dup2(pipein[0], STDIN_FILENO);
92         dup2(pipeout[1], STDOUT_FILENO);
93         dup2(pipeerr[1], STDERR_FILENO);
94         close(pipein[0]);
95         close(pipein[1]);
96         close(pipeout[0]);
97         close(pipeout[1]);
98         close(pipeerr[0]);
99         close(pipeerr[1]);
100
101         char * argv[14];
102         int argc = 0;
103
104         char * tmp = new char[lyxrc.isp_command.length() + 1];
105         lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
106         tmp[lyxrc.isp_command.length()] = '\0';
107         argv[argc++] = tmp;
108         tmp = new char[3];
109         string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
110         argv[argc++] = tmp;
111
112         if (lang != "default") {
113                 tmp = new char[3];
114                 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
115                 argv[argc++] = tmp;
116                 tmp = new char[lang.length() + 1];
117                 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
118                 argv[argc++] = tmp;
119         }
120
121         if (lyxrc.isp_accept_compound) {
122                 // Consider run-together words as legal compounds
123                 tmp = new char[3];
124                 string("-C").copy(tmp, 2); tmp[2] = '\0';
125                 argv[argc++] = tmp;
126         } else {
127                 // Report run-together words with
128                 // missing blanks as errors
129                 tmp = new char[3];
130                 string("-B").copy(tmp, 2); tmp[2] = '\0';
131                 argv[argc++] = tmp;
132         }
133         if (lyxrc.isp_use_esc_chars) {
134                 // Specify additional characters that
135                 // can be part of a word
136                 tmp = new char[3];
137                 string("-w").copy(tmp, 2); tmp[2] = '\0';
138                 argv[argc++] = tmp;
139                 // Put the escape chars in ""s
140                 string tms = '"' + lyxrc.isp_esc_chars + '"';
141                 tmp = new char[tms.length() + 1];
142                 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
143                 argv[argc++] = tmp;
144         }
145         if (lyxrc.isp_use_pers_dict) {
146                 // Specify an alternate personal dictionary
147                 tmp = new char[3];
148                 string("-p").copy(tmp, 2);
149                 tmp[2]= '\0';
150                 argv[argc++] = tmp;
151                 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
152                 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
153                 tmp[lyxrc.isp_pers_dict.length()] = '\0';
154                 argv[argc++] = tmp;
155         }
156         if (lyxrc.isp_use_input_encoding &&
157             params.inputenc != "default") {
158                 string enc = (params.inputenc == "auto")
159                         ? params.language->encoding()->LatexName()
160                         : params.inputenc;
161                 string::size_type n = enc.length();
162                 tmp = new char[3];
163                 string("-T").copy(tmp, 2);
164                 tmp[2] = '\0';
165                 argv[argc++] = tmp; // Input encoding
166                 tmp = new char[n + 1];
167                 enc.copy(tmp, n);
168                 tmp[n] = '\0';
169                 argv[argc++] = tmp;
170         }
171
172         argv[argc++] = 0;
173
174         execvp(argv[0], const_cast<char * const *>(argv));
175
176         // free the memory used by string::copy in the
177         // setup of argv
178         for (int i = 0; i < argc - 1; ++i)
179                 delete[] argv[i];
180
181         lyxerr << "LyX: Failed to start ispell!" << endl;
182         _exit(0);
183 }
184
185
186 } // namespace anon
187
188
189 ISpell::ISpell(BufferParams const & params, string const & lang)
190         : in(0), out(0), inerr(0), str(0)
191 {
192         lyxerr[Debug::GUI] << "Created ispell" << endl;
193
194         // static due to the setvbuf. Ugly.
195         static char o_buf[BUFSIZ];
196
197         // We need to throw an exception not do this
198         pipein[0] = pipein[1] = pipeout[0] = pipeout[1]
199                 = pipeerr[0] = pipeerr[1] = -1;
200
201         // This is what happens when goto gets banned.
202
203         if (pipe(pipein) == -1) {
204                 error_ = _("Can't create pipe for spellchecker.");
205                 return;
206         }
207
208         if (pipe(pipeout) == -1) {
209                 close(pipein[0]);
210                 close(pipein[1]);
211                 error_ = _("Can't create pipe for spellchecker.");
212                 return;
213         }
214
215         if (pipe(pipeerr) == -1) {
216                 close(pipein[0]);
217                 close(pipein[1]);
218                 close(pipeout[0]);
219                 close(pipeout[1]);
220                 error_ = _("Can't create pipe for spellchecker.");
221                 return;
222         }
223
224         if ((out = fdopen(pipein[1], "w")) == 0) {
225                 error_ = _("Can't open pipe for spellchecker.");
226                 return;
227         }
228
229         if ((in = fdopen(pipeout[0], "r")) == 0) {
230                 error_ = _("Can't open pipe for spellchecker.");
231                 return;
232         }
233
234         if ((inerr = fdopen(pipeerr[0], "r")) == 0) {
235                 error_ = _("Can't open pipe for spellchecker.");
236                 return;
237         }
238
239         setvbuf(out, o_buf, _IOLBF, BUFSIZ);
240
241         LaunchIspell * li = new LaunchIspell(params, lang, pipein, pipeout, pipeerr);
242         child_.reset(li);
243         if (li->start() == -1) {
244                 error_ = _("Could not create an ispell process.\nYou may not have "
245                         "the right languages installed.");
246                 child_.reset(0);
247                 return;
248         }
249
250         /* Parent process: Read ispells identification message */
251
252         bool err_read;
253         bool error = select(err_read);
254
255         if (!error) {
256                 if (!err_read) {
257                         // Set terse mode (silently accept correct words)
258                         fputs("!\n", out);
259                         return;
260                 }
261
262                 /* must have read something from stderr */
263                 error_ = buf;
264         } else {
265                 // select returned error
266                 error_ = _("The spell process returned an error.\nPerhaps "
267                                 "it has been configured wrongly ?");
268         }
269
270         close(pipein[0]);
271         close(pipein[1]);
272         close(pipeout[0]);
273         close(pipeout[1]);
274         close(pipeerr[0]);
275         close(pipeerr[1]);
276         child_->kill();
277         child_.reset(0);
278 }
279
280
281 ISpell::~ISpell()
282 {
283         lyxerr[Debug::GUI] << "Killing ispell" << endl;
284
285         if (in)
286                 fclose(in);
287
288         if (inerr)
289                 fclose(inerr);
290
291         if (out) {
292                 fputs("#\n", out); // Save personal dictionary
293
294                 fflush(out);
295                 fclose(out);
296         }
297
298         close(pipein[0]);
299         close(pipein[1]);
300         close(pipeout[0]);
301         close(pipeout[1]);
302         close(pipeerr[0]);
303         close(pipeerr[1]);
304         delete [] str;
305 }
306
307
308 bool ISpell::select(bool & err_read)
309 {
310         fd_set infds;
311         struct timeval tv;
312         int retval = 0;
313         FD_ZERO(&infds);
314         FD_SET(pipeout[0], &infds);
315         FD_SET(pipeerr[0], &infds);
316         tv.tv_sec = 2;
317         tv.tv_usec = 0;
318
319         retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[0]) + 1),
320                         SELECT_TYPE_ARG234 (&infds),
321                         0,
322                         0,
323                         SELECT_TYPE_ARG5 (&tv));
324
325         // error
326         if (retval <= 0)
327                 return true;
328
329         if (FD_ISSET(pipeerr[0], &infds)) {
330                 fgets(buf, BUFSIZ, inerr);
331                 err_read = true;
332                 return false;
333         }
334
335         fgets(buf, BUFSIZ, in);
336         err_read = false;
337         return false;
338 }
339
340
341 string const ISpell::nextMiss()
342 {
343         // Well, somebody is a sick fuck.
344
345         if (str == 0 || *(e+1) == '\0')
346                 return "";
347         char * b = e + 2;
348         e = strpbrk(b, ",\n");
349         *e = '\0';
350         if (b)
351                 return b;
352         return "";
353 }
354
355
356 bool ISpell::alive()
357 {
358         return child_.get() && child_->running();
359 }
360
361
362 enum ISpell::Result ISpell::check(WordLangTuple const & word)
363 {
364         // FIXME Please rewrite to use string.
365
366         Result res;
367
368         ::fputs(word.word().c_str(), out);
369         ::fputc('\n', out);
370
371         bool err_read;
372         bool error = select(err_read);
373
374         if (error) {
375                 error_ = _("Could not communicate with the spell-checker program.");
376                 return UNKNOWN;
377         }
378
379         if (err_read) {
380                 error_ = buf;
381                 return UNKNOWN;
382         }
383
384         // I think we have to check if ispell is still alive here because
385         // the signal-handler could have disabled blocking on the fd
386         if (!alive())
387                 return UNKNOWN;
388
389         switch (*buf) {
390         case '*':
391                 res = OK;
392                 break;
393         case '+':
394                 res = ROOT;
395                 break;
396         case '-':
397                 res = COMPOUNDWORD;
398                 break;
399         case '\n':
400                 res = IGNORE;
401                 break;
402         case '#': // Not found, no near misses and guesses
403                 res = UNKNOWN;
404                 break;
405         case '?': // Not found, and no near misses, but guesses (guesses are ignored)
406         case '&': // Not found, but we have near misses
407         {
408                 res = MISSED;
409                 char * p = strpbrk(buf, ":");
410                 str = new char[strlen(p) + 1];
411                 e   = str;
412                 strcpy(str, p);
413                 break;
414         }
415         default: // This shouldn't happen, but you know Murphy
416                 res = UNKNOWN;
417         }
418
419         *buf = 0;
420         if (res != IGNORE) {
421                 /* wait for ispell to finish */
422                 while (*buf!= '\n')
423                         fgets(buf, 255, in);
424         }
425         return res;
426 }
427
428
429 void ISpell::insert(WordLangTuple const & word)
430 {
431         ::fputc('*', out); // Insert word in personal dictionary
432         ::fputs(word.word().c_str(), out);
433         ::fputc('\n', out);
434 }
435
436
437 void ISpell::accept(WordLangTuple const & word)
438 {
439         ::fputc('@', out); // Accept in this session
440         ::fputs(word.word().c_str(), out);
441         ::fputc('\n', out);
442 }
443
444
445 string const ISpell::error()
446 {
447         return error_;
448 }