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