fix cmake
[lyx.git] / src / ISpell.cpp
blobbd772685c7b9fc8dbc70ae94099d41e93eab70d4
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.
6 * \author unknown
7 * \author Angus Leeming
8 * \author John Levon
10 * Full author contact details are available in file CREDITS.
13 #include <config.h>
15 #include "ISpell.h"
17 #include "BufferParams.h"
18 #include "Encoding.h"
19 #include "Language.h"
20 #include "LyXRC.h"
21 #include "WordLangTuple.h"
23 #include "support/debug.h"
24 #include "support/gettext.h"
25 #include "support/ForkedCalls.h"
26 #include "support/lstrings.h"
27 #include "support/unicode.h"
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
37 using boost::shared_ptr;
39 using namespace std;
40 using namespace lyx::support;
42 namespace lyx {
44 namespace {
46 class LaunchIspell : public ForkedProcess
48 public:
49 ///
50 LaunchIspell(BufferParams const & p, string const & l,
51 int * in, int * out, int * err)
52 : params(p), lang(l), pipein(in), pipeout(out), pipeerr(err) {}
53 ///
54 virtual shared_ptr<ForkedProcess> clone() const {
55 return shared_ptr<ForkedProcess>(new LaunchIspell(*this));
57 ///
58 int start();
59 private:
60 ///
61 virtual int generateChild();
63 ///
64 BufferParams const & params;
65 string const & lang;
66 int * const pipein;
67 int * const pipeout;
68 int * const pipeerr;
72 int LaunchIspell::start()
74 command_ = lyxrc.isp_command;
75 return run(DontWait);
79 int LaunchIspell::generateChild()
81 pid_t isp_pid = fork();
83 if (isp_pid != 0) {
84 // failed (-1) or parent process (>0)
85 return isp_pid;
88 // child process
89 dup2(pipein[0], STDIN_FILENO);
90 dup2(pipeout[1], STDOUT_FILENO);
91 dup2(pipeerr[1], STDERR_FILENO);
92 close(pipein[0]);
93 close(pipein[1]);
94 close(pipeout[0]);
95 close(pipeout[1]);
96 close(pipeerr[0]);
97 close(pipeerr[1]);
99 char * argv[14];
100 int argc = 0;
102 char * tmp = new char[lyxrc.isp_command.length() + 1];
103 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
104 tmp[lyxrc.isp_command.length()] = '\0';
105 argv[argc++] = tmp;
106 tmp = new char[3];
107 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
108 argv[argc++] = tmp;
110 if (lang != "default") {
111 tmp = new char[3];
112 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
113 argv[argc++] = tmp;
114 tmp = new char[lang.length() + 1];
115 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
116 argv[argc++] = tmp;
119 if (lyxrc.isp_accept_compound) {
120 // Consider run-together words as legal compounds
121 tmp = new char[3];
122 string("-C").copy(tmp, 2); tmp[2] = '\0';
123 argv[argc++] = tmp;
124 } else {
125 // Report run-together words with
126 // missing blanks as errors
127 tmp = new char[3];
128 string("-B").copy(tmp, 2); tmp[2] = '\0';
129 argv[argc++] = tmp;
131 if (lyxrc.isp_use_esc_chars) {
132 // Specify additional characters that
133 // can be part of a word
134 tmp = new char[3];
135 string("-w").copy(tmp, 2); tmp[2] = '\0';
136 argv[argc++] = tmp;
137 // Put the escape chars in ""s
138 string tms = '"' + lyxrc.isp_esc_chars + '"';
139 tmp = new char[tms.length() + 1];
140 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
141 argv[argc++] = tmp;
143 if (lyxrc.isp_use_pers_dict) {
144 // Specify an alternate personal dictionary
145 tmp = new char[3];
146 string("-p").copy(tmp, 2);
147 tmp[2]= '\0';
148 argv[argc++] = tmp;
149 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
150 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
151 tmp[lyxrc.isp_pers_dict.length()] = '\0';
152 argv[argc++] = tmp;
154 if (lyxrc.isp_use_input_encoding &&
155 params.inputenc != "default") {
156 string enc = (params.inputenc == "auto")
157 ? params.language->encoding()->latexName()
158 : params.inputenc;
159 size_t const n = enc.length();
160 tmp = new char[3];
161 string("-T").copy(tmp, 2);
162 tmp[2] = '\0';
163 argv[argc++] = tmp; // Input encoding
164 tmp = new char[n + 1];
165 enc.copy(tmp, n);
166 tmp[n] = '\0';
167 argv[argc++] = tmp;
170 argv[argc++] = 0;
172 execvp(argv[0], const_cast<char * const *>(argv));
174 // free the memory used by string::copy in the
175 // setup of argv
176 for (int i = 0; i < argc - 1; ++i)
177 delete[] argv[i];
179 lyxerr << "LyX: Failed to start ispell!" << endl;
180 _exit(0);
184 string const to_iconv_encoding(docstring const & s, string const & encoding)
186 if (lyxrc.isp_use_input_encoding) {
187 vector<char> const encoded =
188 ucs4_to_eightbit(s.data(), s.length(), encoding);
189 return string(encoded.begin(), encoded.end());
191 // FIXME UNICODE: we don't need to convert to UTF8, but probably to the locale encoding
192 return to_utf8(s);
196 docstring const from_iconv_encoding(string const & s, string const & encoding)
198 if (lyxrc.isp_use_input_encoding) {
199 vector<char_type> const ucs4 =
200 eightbit_to_ucs4(s.data(), s.length(), encoding);
201 return docstring(ucs4.begin(), ucs4.end());
203 // FIXME UNICODE: s is not in UTF8, but probably the locale encoding
204 return from_utf8(s);
207 } // namespace anon
210 ISpell::ISpell(BufferParams const & params, string const & lang)
211 : in(0), out(0), inerr(0), str(0)
213 //LYXERR(Debug::GUI, "Created ispell");
215 encoding = params.encoding().iconvName();
217 // static due to the setvbuf. Ugly.
218 static char o_buf[BUFSIZ];
220 // We need to throw an exception not do this
221 pipein[0] = pipein[1] = pipeout[0] = pipeout[1]
222 = pipeerr[0] = pipeerr[1] = -1;
224 // This is what happens when goto gets banned.
226 if (pipe(pipein) == -1) {
227 error_ = _("Can't create pipe for spellchecker.");
228 return;
231 if (pipe(pipeout) == -1) {
232 close(pipein[0]);
233 close(pipein[1]);
234 error_ = _("Can't create pipe for spellchecker.");
235 return;
238 if (pipe(pipeerr) == -1) {
239 close(pipein[0]);
240 close(pipein[1]);
241 close(pipeout[0]);
242 close(pipeout[1]);
243 error_ = _("Can't create pipe for spellchecker.");
244 return;
247 if ((out = fdopen(pipein[1], "w")) == 0) {
248 error_ = _("Can't open pipe for spellchecker.");
249 return;
252 if ((in = fdopen(pipeout[0], "r")) == 0) {
253 error_ = _("Can't open pipe for spellchecker.");
254 return;
257 if ((inerr = fdopen(pipeerr[0], "r")) == 0) {
258 error_ = _("Can't open pipe for spellchecker.");
259 return;
262 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
264 LaunchIspell * li = new LaunchIspell(params, lang, pipein, pipeout, pipeerr);
265 child_.reset(li);
266 if (li->start() == -1) {
267 error_ = _("Could not create an ispell process.\nYou may not have "
268 "the right languages installed.");
269 child_.reset(0);
270 return;
273 // Parent process: Read ispells identification message
275 bool err_read;
276 bool error = select(err_read);
278 if (!error) {
279 if (!err_read) {
280 // Set terse mode (silently accept correct words)
281 fputs("!\n", out);
282 return;
285 // must have read something from stderr
286 // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
287 error_ = from_utf8(buf);
288 } else {
289 // select returned error
290 error_ = _("The ispell process returned an error.\nPerhaps "
291 "it has been configured wrongly ?");
294 close(pipein[0]);
295 close(pipein[1]);
296 close(pipeout[0]);
297 close(pipeout[1]);
298 close(pipeerr[0]);
299 close(pipeerr[1]);
300 child_->kill();
301 child_.reset(0);
305 ISpell::~ISpell()
307 //LYXERR(Debug::GUI, "Killing ispell");
309 if (in)
310 fclose(in);
312 if (inerr)
313 fclose(inerr);
315 if (out) {
316 fputs("#\n", out); // Save personal dictionary
318 fflush(out);
319 fclose(out);
322 close(pipein[0]);
323 close(pipein[1]);
324 close(pipeout[0]);
325 close(pipeout[1]);
326 close(pipeerr[0]);
327 close(pipeerr[1]);
328 delete [] str;
332 bool ISpell::select(bool & err_read)
334 fd_set infds;
335 struct timeval tv;
336 int retval = 0;
337 FD_ZERO(&infds);
338 FD_SET(pipeout[0], &infds);
339 FD_SET(pipeerr[0], &infds);
340 tv.tv_sec = 2;
341 tv.tv_usec = 0;
343 retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[0]) + 1),
344 SELECT_TYPE_ARG234 (&infds),
347 SELECT_TYPE_ARG5 (&tv));
349 // error
350 if (retval <= 0)
351 return true;
353 if (FD_ISSET(pipeerr[0], &infds)) {
354 fgets(buf, BUFSIZ, inerr);
355 err_read = true;
356 return false;
359 fgets(buf, BUFSIZ, in);
360 err_read = false;
361 return false;
365 docstring const ISpell::nextMiss()
367 // Well, somebody is a sick fuck.
369 if (str == 0 || *(e+1) == '\0')
370 return docstring();
371 char * b = e + 2;
372 e = strpbrk(b, ",\n");
373 *e = '\0';
374 if (b)
375 return from_iconv_encoding(b, encoding);
376 return docstring();
380 bool ISpell::alive()
382 return child_.get() && child_->running();
386 enum ISpell::Result ISpell::check(WordLangTuple const & word)
388 // FIXME Please rewrite to use string.
390 Result res;
392 string const encoded = to_iconv_encoding(word.word(), encoding);
393 if (encoded.empty()) {
394 error_ = bformat(
395 _("Could not check word `%1$s' because it could not be converted to encoding `%2$s'."),
396 word.word(), from_ascii(encoding));
397 return UNKNOWN_WORD;
399 ::fputs(encoded.c_str(), out);
400 ::fputc('\n', out);
402 bool err_read;
403 bool error = select(err_read);
405 if (error) {
406 error_ = _("Could not communicate with the ispell spellchecker process.");
407 return UNKNOWN_WORD;
410 if (err_read) {
411 // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
412 error_ = from_utf8(buf);
413 return UNKNOWN_WORD;
416 // I think we have to check if ispell is still alive here because
417 // the signal-handler could have disabled blocking on the fd
418 if (!alive())
419 return UNKNOWN_WORD;
421 switch (*buf) {
422 case '*':
423 res = OK;
424 break;
425 case '+':
426 res = ROOT;
427 break;
428 case '-':
429 res = COMPOUND_WORD;
430 break;
431 case '\n':
432 res = IGNORED_WORD;
433 break;
434 case '#': // Not found, no near misses and guesses
435 res = UNKNOWN_WORD;
436 break;
437 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
438 case '&': // Not found, but we have near misses
440 res = SUGGESTED_WORDS;
441 char * p = strpbrk(buf, ":");
442 str = new char[strlen(p) + 1];
443 e = str;
444 strcpy(str, p);
445 break;
447 default: // This shouldn't happen, but you know Murphy
448 res = UNKNOWN_WORD;
451 *buf = 0;
452 if (res != IGNORED_WORD) {
453 /* wait for ispell to finish */
454 while (*buf!= '\n')
455 fgets(buf, 255, in);
457 return res;
461 void ISpell::insert(WordLangTuple const & word)
463 string const encoded = to_iconv_encoding(word.word(), encoding);
464 if (encoded.empty()) {
465 error_ = bformat(
466 _("Could not insert word `%1$s' because it could not be converted to encoding `%2$s'."),
467 word.word(), from_ascii(encoding));
468 return;
470 ::fputc('*', out); // Insert word in personal dictionary
471 ::fputs(encoded.c_str(), out);
472 ::fputc('\n', out);
476 void ISpell::accept(WordLangTuple const & word)
478 string const encoded = to_iconv_encoding(word.word(), encoding);
479 if (encoded.empty()) {
480 error_ = bformat(
481 _("Could not accept word `%1$s' because it could not be converted to encoding `%2$s'."),
482 word.word(), from_ascii(encoding));
483 return;
485 ::fputc('@', out); // Accept in this session
486 ::fputs(encoded.c_str(), out);
487 ::fputc('\n', out);
491 docstring const ISpell::error()
493 return error_;
497 } // namespace lyx