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