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