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