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