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