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