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