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