]> git.lyx.org Git - lyx.git/blob - src/ispell.C
update no.po
[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 #ifdef __GNUG__
13 #pragma implementation
14 #endif
15
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <unistd.h>
19 #include <fcntl.h>
20 #include <cstdio>
21
22 // FIXME: do we need any of this horrible gook ?
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
25 # include <ctime>
26 #else
27 # if HAVE_SYS_TIME_H
28 #  include <sys/time.h>
29 # else
30 #  include <ctime>
31 # endif
32 #endif
33
34 #ifdef HAVE_SYS_SELECT_H
35 # ifdef HAVE_STRINGS_H
36    // <strings.h> is needed at least on AIX because FD_ZERO uses bzero().
37    // BUT we cannot include both string.h and strings.h on Irix 6.5 :(
38 #  ifdef _AIX
39 #   include <strings.h>
40 #  endif
41 # endif
42 #include <sys/select.h>
43 #endif
44
45 #include "LString.h"
46 #include "lyxrc.h"
47 #include "language.h"
48 #include "debug.h"
49 #include "encoding.h"
50 #include "ispell.h"
51 #include "WordLangTuple.h"
52
53 #include "support/forkedcall.h"
54 #include "support/lstrings.h"
55
56 #ifndef CXX_GLOBAL_CSTD
57 using std::strcpy;
58 using std::strlen;
59 using std::strpbrk;
60 using std::strstr;
61 #endif
62
63 using std::endl;
64
65 namespace {
66
67 /// pid for the `ispell' process.
68 pid_t isp_pid = -1;
69
70 class LaunchIspell : public ForkedProcess {
71 public:
72         ///
73         LaunchIspell(BufferParams const & p, string const & l,
74                      int * in, int * out)
75                 : params(p), lang(l), pipein(in), pipeout(out) {}
76         ///
77         virtual ForkedProcess * clone() const {
78                 return new LaunchIspell(*this);
79         }
80         ///
81         int start();
82 private:
83         ///
84         virtual int generateChild();
85
86         ///
87         BufferParams const & params;
88         string const & lang;
89         int * const pipein;
90         int * const pipeout;
91 };
92
93
94 int LaunchIspell::start()
95 {
96         command_ = "ispell";
97         return runNonBlocking();
98 }
99
100
101 int LaunchIspell::generateChild()
102 {
103         isp_pid = fork();
104
105         if (isp_pid != 0) {
106                 // failed (-1) or parent process (>0)
107                 return isp_pid;
108         }
109
110         // child process
111         dup2(pipein[0], STDIN_FILENO);
112         dup2(pipeout[1], STDOUT_FILENO);
113         ::close(pipein[0]);
114         ::close(pipein[1]);
115         ::close(pipeout[0]);
116         ::close(pipeout[1]);
117
118         char * argv[14];
119         int argc = 0;
120
121         char * tmp = new char[lyxrc.isp_command.length() + 1];
122         lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
123         tmp[lyxrc.isp_command.length()] = '\0';
124         argv[argc++] = tmp;
125         tmp = new char[3];
126         string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
127         argv[argc++] = tmp;
128
129         if (lang != "default") {
130                 tmp = new char[3];
131                 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
132                 argv[argc++] = tmp;
133                 tmp = new char[lang.length() + 1];
134                 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
135                 argv[argc++] = tmp;
136         }
137
138         if (lyxrc.isp_accept_compound) {
139                 // Consider run-together words as legal compounds
140                 tmp = new char[3];
141                 string("-C").copy(tmp, 2); tmp[2] = '\0';
142                 argv[argc++] = tmp;
143         } else {
144                 // Report run-together words with
145                 // missing blanks as errors
146                 tmp = new char[3];
147                 string("-B").copy(tmp, 2); tmp[2] = '\0';
148                 argv[argc++] = tmp;
149         }
150         if (lyxrc.isp_use_esc_chars) {
151                 // Specify additional characters that
152                 // can be part of a word
153                 tmp = new char[3];
154                 string("-w").copy(tmp, 2); tmp[2] = '\0';
155                 argv[argc++] = tmp;
156                 // Put the escape chars in ""s
157                 string tms = '"' + lyxrc.isp_esc_chars + '"';
158                 tmp = new char[tms.length() + 1];
159                 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
160                 argv[argc++] = tmp;
161         }
162         if (lyxrc.isp_use_pers_dict) {
163                 // Specify an alternate personal dictionary
164                 tmp = new char[3];
165                 string("-p").copy(tmp, 2);
166                 tmp[2]= '\0';
167                 argv[argc++] = tmp;
168                 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
169                 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
170                 tmp[lyxrc.isp_pers_dict.length()] = '\0';
171                 argv[argc++] = tmp;
172         }
173         if (lyxrc.isp_use_input_encoding &&
174             params.inputenc != "default") {
175                 string enc = (params.inputenc == "auto")
176                         ? params.language->encoding()->LatexName()
177                         : params.inputenc;
178                 string::size_type n = enc.length();
179                 tmp = new char[3];
180                 string("-T").copy(tmp, 2);
181                 tmp[2] = '\0';
182                 argv[argc++] = tmp; // Input encoding
183                 tmp = new char[n + 1];
184                 enc.copy(tmp, n);
185                 tmp[n] = '\0';
186                 argv[argc++] = tmp;
187         }
188
189         argv[argc++] = 0;
190
191         execvp(argv[0], const_cast<char * const *>(argv));
192
193         // free the memory used by string::copy in the
194         // setup of argv
195         for (int i = 0; i < argc - 1; ++i)
196                 delete[] argv[i];
197
198         lyxerr << "LyX: Failed to start ispell!" << endl;
199         _exit(0);
200 }
201
202
203 } // namespace anon
204
205
206 ISpell::ISpell(BufferParams const & params, string const & lang)
207         : str(0)
208 {
209         static char o_buf[BUFSIZ];  // jc: it could be smaller
210         int pipein[2];
211         int pipeout[2];
212
213         isp_pid = -1;
214
215         if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
216                 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
217                 setError();
218                 return;
219         }
220
221         if ((out = fdopen(pipein[1], "w")) == 0) {
222                 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
223                        << endl;
224                 setError();
225                 return;
226         }
227
228         if ((in = fdopen(pipeout[0], "r")) == 0) {
229                 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
230                        << endl;
231                 setError();
232                 return;
233         }
234
235         setvbuf(out, o_buf, _IOLBF, BUFSIZ);
236
237         isp_fd = pipeout[0];
238
239         LaunchIspell childprocess(params, lang, pipein, pipeout);
240         isp_pid = childprocess.start();
241         if (isp_pid == -1) {
242                 lyxerr << "LyX: Can't create child process for spellchecker!"
243                        << endl;
244                 setError();
245                 return;
246         }
247
248         setError();
249         /* Parent process: Read ispells identification message */
250         // Hmm...what are we using this id msg for? Nothing? (Lgb)
251         // Actually I used it to tell if it's truly Ispell or if it's
252         // aspell -- (kevinatk@home.com)
253         // But no code actually used the results for anything useful
254         // so I removed it again. Perhaps we can remove this code too.
255         // - jbl
256         char buf[2048];
257         fd_set infds;
258         struct timeval tv;
259         int retval = 0;
260         FD_ZERO(&infds);
261         FD_SET(pipeout[0], &infds);
262         tv.tv_sec = 15; // fifteen second timeout. Probably too much,
263         // but it can't really hurt.
264         tv.tv_usec = 0;
265
266         // Configure provides us with macros which are supposed to do
267         // the right typecast.
268         retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
269                         SELECT_TYPE_ARG234 (&infds),
270                         0,
271                         0,
272                         SELECT_TYPE_ARG5 (&tv));
273
274         if (retval > 0) {
275                 // Ok, do the reading. We don't have to FD_ISSET since
276                 // there is only one fd in infds.
277                 fgets(buf, 2048, in);
278
279                 fputs("!\n", out); // Set terse mode (silently accept correct words)
280
281         } else if (retval == 0) {
282                 // timeout. Give nice message to user.
283                 lyxerr << "Ispell read timed out, what now?" << endl;
284                 // This probably works but could need some thought
285                 isp_pid = -1;
286                 ::close(pipeout[0]);
287                 ::close(pipeout[1]);
288                 ::close(pipein[0]);
289                 ::close(pipein[1]);
290                 isp_fd = -1;
291         } else {
292                 // Select returned error
293                 lyxerr << "Select on ispell returned error, what now?" << endl;
294         }
295 }
296
297
298 ISpell::~ISpell()
299 {
300         delete[] str;
301 }
302
303
304 void ISpell::setError()
305 {
306         if (isp_pid == -1) {
307                 error_ =
308                         "\n\n"
309                         "The spellcheck-process has died for some reason.\n"
310                         "*One* possible reason could be that you do not have\n"
311                         "a dictionary file for the language of this document\n"
312                         "installed.\n"
313                         "Check your spellchecker or set another dictionary\n"
314                         "in the Spellchecker Options menu.\n\n";
315         } else {
316                 error_ = 0;
317         }
318 }
319
320
321 string const ISpell::nextMiss()
322 {
323         if (str == 0 || *(e+1) == '\0')
324                 return "";
325         char * b = e + 2;
326         e = strpbrk(b, ",\n");
327         *e = '\0';
328         if (b)
329                 return b;
330         return "";
331 }
332
333
334 bool ISpell::alive()
335 {
336         return isp_pid != -1;
337 }
338
339
340 void ISpell::cleanUp()
341 {
342         ::fclose(out);
343 }
344
345
346 enum ISpell::Result ISpell::check(WordLangTuple const & word)
347 {
348         // FIXME Please rewrite to use string.
349
350         Result res;
351
352         ::fputs(word.word().c_str(), out);
353         ::fputc('\n', out);
354
355         char buf[1024];
356         ::fgets(buf, 1024, in);
357
358         // I think we have to check if ispell is still alive here because
359         // the signal-handler could have disabled blocking on the fd
360         if (!alive())
361                 return UNKNOWN;
362
363         switch (*buf) {
364         case '*':
365                 res = OK;
366                 break;
367         case '+':
368                 res = ROOT;
369                 break;
370         case '-':
371                 res = COMPOUNDWORD;
372                 break;
373         case '\n':
374                 res = IGNORE;
375                 break;
376         case '#': // Not found, no near misses and guesses
377                 res = UNKNOWN;
378                 break;
379         case '?': // Not found, and no near misses, but guesses (guesses are ignored)
380         case '&': // Not found, but we have near misses
381         {
382                 res = MISSED;
383                 char * p = strpbrk(buf, ":");
384                 str = new char[strlen(p) + 1];
385                 e   = str;
386                 strcpy(str, p);
387                 break;
388         }
389         default: // This shouldn't happen, but you know Murphy
390                 res = UNKNOWN;
391         }
392
393         *buf = 0;
394         if (res != IGNORE) {
395                 /* wait for ispell to finish */
396                 while (*buf!= '\n')
397                         fgets(buf, 255, in);
398         }
399         return res;
400 }
401
402
403 void ISpell::close()
404 {
405         // Note: If you decide to optimize this out when it is not
406         // needed please note that when Aspell is used this command
407         // is also needed to save the replacement dictionary.
408         // -- Kevin Atkinson (kevinatk@home.com)
409
410         fputs("#\n", out); // Save personal dictionary
411
412         fflush(out);
413         fclose(out);
414 }
415
416
417 void ISpell::insert(WordLangTuple const & word)
418 {
419         ::fputc('*', out); // Insert word in personal dictionary
420         ::fputs(word.word().c_str(), out);
421         ::fputc('\n', out);
422 }
423
424
425 void ISpell::accept(WordLangTuple const & word)
426 {
427         ::fputc('@', out); // Accept in this session
428         ::fputs(word.word().c_str(), out);
429         ::fputc('\n', out);
430 }
431
432
433 string const ISpell::error()
434 {
435         if (error_)
436                 return error_;
437         return "";
438 }