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