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