3 * Copyright 2002 the LyX Team
4 * Read the file COPYING
7 * \author John Levon <levon@movementarian.org>
13 #pragma implementation
16 #include <sys/types.h>
22 // FIXME: do we need any of this horrible gook ?
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
28 # include <sys/time.h>
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 :(
42 #include <sys/select.h>
51 #include "WordLangTuple.h"
53 #include "support/forkedcall.h"
54 #include "support/lstrings.h"
56 #ifndef CXX_GLOBAL_CSTD
67 /// pid for the `ispell' process.
70 class LaunchIspell : public ForkedProcess {
73 LaunchIspell(BufferParams const & p, string const & l,
75 : params(p), lang(l), pipein(in), pipeout(out) {}
77 virtual ForkedProcess * clone() const {
78 return new LaunchIspell(*this);
84 virtual int generateChild();
87 BufferParams const & params;
94 int LaunchIspell::start()
97 return runNonBlocking();
101 int LaunchIspell::generateChild()
106 // failed (-1) or parent process (>0)
111 dup2(pipein[0], STDIN_FILENO);
112 dup2(pipeout[1], STDOUT_FILENO);
121 char * tmp = new char[lyxrc.isp_command.length() + 1];
122 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
123 tmp[lyxrc.isp_command.length()] = '\0';
126 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
129 if (lang != "default") {
131 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
133 tmp = new char[lang.length() + 1];
134 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
138 if (lyxrc.isp_accept_compound) {
139 // Consider run-together words as legal compounds
141 string("-C").copy(tmp, 2); tmp[2] = '\0';
144 // Report run-together words with
145 // missing blanks as errors
147 string("-B").copy(tmp, 2); tmp[2] = '\0';
150 if (lyxrc.isp_use_esc_chars) {
151 // Specify additional characters that
152 // can be part of a word
154 string("-w").copy(tmp, 2); tmp[2] = '\0';
156 // Put the escape chars in ""s
157 string tms = '"' + lyxrc.isp_esc_chars + '"';
158 tmp = new char[tms.length() + 1];
159 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
162 if (lyxrc.isp_use_pers_dict) {
163 // Specify an alternate personal dictionary
165 string("-p").copy(tmp, 2);
168 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
169 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
170 tmp[lyxrc.isp_pers_dict.length()] = '\0';
173 if (lyxrc.isp_use_input_encoding &&
174 params.inputenc != "default") {
175 string enc = (params.inputenc == "auto")
176 ? params.language->encoding()->LatexName()
178 string::size_type n = enc.length();
180 string("-T").copy(tmp, 2);
182 argv[argc++] = tmp; // Input encoding
183 tmp = new char[n + 1];
191 execvp(argv[0], const_cast<char * const *>(argv));
193 // free the memory used by string::copy in the
195 for (int i = 0; i < argc - 1; ++i)
198 lyxerr << "LyX: Failed to start ispell!" << endl;
206 ISpell::ISpell(BufferParams const & params, string const & lang)
209 static char o_buf[BUFSIZ]; // jc: it could be smaller
215 if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
216 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
221 if ((out = fdopen(pipein[1], "w")) == 0) {
222 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
228 if ((in = fdopen(pipeout[0], "r")) == 0) {
229 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
235 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
239 LaunchIspell childprocess(params, lang, pipein, pipeout);
240 isp_pid = childprocess.start();
242 lyxerr << "LyX: Can't create child process for spellchecker!"
249 /* Parent process: Read ispells identification message */
250 // Hmm...what are we using this id msg for? Nothing? (Lgb)
251 // Actually I used it to tell if it's truly Ispell or if it's
252 // aspell -- (kevinatk@home.com)
253 // But no code actually used the results for anything useful
254 // so I removed it again. Perhaps we can remove this code too.
261 FD_SET(pipeout[0], &infds);
262 tv.tv_sec = 15; // fifteen second timeout. Probably too much,
263 // but it can't really hurt.
266 // Configure provides us with macros which are supposed to do
267 // the right typecast.
268 retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
269 SELECT_TYPE_ARG234 (&infds),
272 SELECT_TYPE_ARG5 (&tv));
275 // Ok, do the reading. We don't have to FD_ISSET since
276 // there is only one fd in infds.
277 fgets(buf, 2048, in);
279 fputs("!\n", out); // Set terse mode (silently accept correct words)
281 } else if (retval == 0) {
282 // timeout. Give nice message to user.
283 lyxerr << "Ispell read timed out, what now?" << endl;
284 // This probably works but could need some thought
292 // Select returned error
293 lyxerr << "Select on ispell returned error, what now?" << endl;
304 void ISpell::setError()
309 "The spellcheck-process has died for some reason.\n"
310 "*One* possible reason could be that you do not have\n"
311 "a dictionary file for the language of this document\n"
313 "Check your spellchecker or set another dictionary\n"
314 "in the Spellchecker Options menu.\n\n";
321 string const ISpell::nextMiss()
323 if (str == 0 || *(e+1) == '\0')
326 e = strpbrk(b, ",\n");
336 return isp_pid != -1;
340 void ISpell::cleanUp()
346 enum ISpell::Result ISpell::check(WordLangTuple const & word)
348 // FIXME Please rewrite to use string.
352 ::fputs(word.word().c_str(), out);
356 ::fgets(buf, 1024, in);
358 // I think we have to check if ispell is still alive here because
359 // the signal-handler could have disabled blocking on the fd
376 case '#': // Not found, no near misses and guesses
379 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
380 case '&': // Not found, but we have near misses
383 char * p = strpbrk(buf, ":");
384 str = new char[strlen(p) + 1];
389 default: // This shouldn't happen, but you know Murphy
395 /* wait for ispell to finish */
405 // Note: If you decide to optimize this out when it is not
406 // needed please note that when Aspell is used this command
407 // is also needed to save the replacement dictionary.
408 // -- Kevin Atkinson (kevinatk@home.com)
410 fputs("#\n", out); // Save personal dictionary
417 void ISpell::insert(WordLangTuple const & word)
419 ::fputc('*', out); // Insert word in personal dictionary
420 ::fputs(word.word().c_str(), out);
425 void ISpell::accept(WordLangTuple const & word)
427 ::fputc('@', out); // Accept in this session
428 ::fputs(word.word().c_str(), out);
433 string const ISpell::error()