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