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