3 * Copyright 2002 the LyX Team
4 * Read the file COPYING
7 * \author John Levon <levon@movementarian.org>
18 #include "WordLangTuple.h"
20 #include "bufferparams.h"
22 #include "support/forkedcall.h"
23 #include "support/lstrings.h"
25 // HP-UX 11.x doesn't have this header
26 #ifdef HAVE_SYS_SELECT_H
27 #include <sys/select.h>
31 using namespace lyx::support;
33 #ifndef CXX_GLOBAL_CSTD
45 class LaunchIspell : public lyx::support::ForkedProcess {
48 LaunchIspell(BufferParams const & p, string const & l,
49 int * in, int * out, int * err)
50 : params(p), lang(l), pipein(in), pipeout(out), pipeerr(err) {}
52 virtual lyx::support::ForkedProcess * clone() const {
53 return new LaunchIspell(*this);
59 virtual int generateChild();
62 BufferParams const & params;
70 int LaunchIspell::start()
72 command_ = lyxrc.isp_command;
73 return runNonBlocking();
77 int LaunchIspell::generateChild()
79 pid_t isp_pid = fork();
82 // failed (-1) or parent process (>0)
87 dup2(pipein[0], STDIN_FILENO);
88 dup2(pipeout[1], STDOUT_FILENO);
89 dup2(pipeerr[1], STDERR_FILENO);
100 char * tmp = new char[lyxrc.isp_command.length() + 1];
101 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
102 tmp[lyxrc.isp_command.length()] = '\0';
105 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
108 if (lang != "default") {
110 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
112 tmp = new char[lang.length() + 1];
113 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
117 if (lyxrc.isp_accept_compound) {
118 // Consider run-together words as legal compounds
120 string("-C").copy(tmp, 2); tmp[2] = '\0';
123 // Report run-together words with
124 // missing blanks as errors
126 string("-B").copy(tmp, 2); tmp[2] = '\0';
129 if (lyxrc.isp_use_esc_chars) {
130 // Specify additional characters that
131 // can be part of a word
133 string("-w").copy(tmp, 2); tmp[2] = '\0';
135 // Put the escape chars in ""s
136 string tms = '"' + lyxrc.isp_esc_chars + '"';
137 tmp = new char[tms.length() + 1];
138 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
141 if (lyxrc.isp_use_pers_dict) {
142 // Specify an alternate personal dictionary
144 string("-p").copy(tmp, 2);
147 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
148 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
149 tmp[lyxrc.isp_pers_dict.length()] = '\0';
152 if (lyxrc.isp_use_input_encoding &&
153 params.inputenc != "default") {
154 string enc = (params.inputenc == "auto")
155 ? params.language->encoding()->LatexName()
157 string::size_type n = enc.length();
159 string("-T").copy(tmp, 2);
161 argv[argc++] = tmp; // Input encoding
162 tmp = new char[n + 1];
170 execvp(argv[0], const_cast<char * const *>(argv));
172 // free the memory used by string::copy in the
174 for (int i = 0; i < argc - 1; ++i)
177 lyxerr << "LyX: Failed to start ispell!" << endl;
185 ISpell::ISpell(BufferParams const & params, string const & lang)
186 : in(0), out(0), inerr(0), str(0)
188 lyxerr[Debug::GUI] << "Created ispell" << endl;
190 // static due to the setvbuf. Ugly.
191 static char o_buf[BUFSIZ];
193 // We need to throw an exception not do this
194 pipein[0] = pipein[1] = pipeout[0] = pipeout[1]
195 = pipeerr[0] = pipeerr[1] = -1;
197 // This is what happens when goto gets banned.
199 if (pipe(pipein) == -1) {
200 error_ = _("Can't create pipe for spellchecker.");
204 if (pipe(pipeout) == -1) {
207 error_ = _("Can't create pipe for spellchecker.");
211 if (pipe(pipeerr) == -1) {
216 error_ = _("Can't create pipe for spellchecker.");
220 if ((out = fdopen(pipein[1], "w")) == 0) {
221 error_ = _("Can't open pipe for spellchecker.");
225 if ((in = fdopen(pipeout[0], "r")) == 0) {
226 error_ = _("Can't open pipe for spellchecker.");
230 if ((inerr = fdopen(pipeerr[0], "r")) == 0) {
231 error_ = _("Can't open pipe for spellchecker.");
235 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
237 LaunchIspell * li = new LaunchIspell(params, lang, pipein, pipeout, pipeerr);
239 if (li->start() == -1) {
240 error_ = _("Could not create an ispell process.\nYou may not have "
241 "the right languages installed.");
246 /* Parent process: Read ispells identification message */
249 bool error = select(err_read);
253 // Set terse mode (silently accept correct words)
258 /* must have read something from stderr */
261 // select returned error
262 error_ = _("The spell process returned an error.\nPerhaps "
263 "it has been configured wrongly ?");
279 lyxerr[Debug::GUI] << "Killing ispell" << endl;
288 fputs("#\n", out); // Save personal dictionary
304 bool ISpell::select(bool & err_read)
310 FD_SET(pipeout[0], &infds);
311 FD_SET(pipeerr[0], &infds);
315 retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[0]) + 1),
316 SELECT_TYPE_ARG234 (&infds),
319 SELECT_TYPE_ARG5 (&tv));
325 if (FD_ISSET(pipeerr[0], &infds)) {
326 fgets(buf, BUFSIZ, inerr);
331 fgets(buf, BUFSIZ, in);
337 string const ISpell::nextMiss()
339 // Well, somebody is a sick fuck.
341 if (str == 0 || *(e+1) == '\0')
344 e = strpbrk(b, ",\n");
354 return child_.get() && child_->running();
358 enum ISpell::Result ISpell::check(WordLangTuple const & word)
360 // FIXME Please rewrite to use string.
364 ::fputs(word.word().c_str(), out);
368 bool error = select(err_read);
371 error_ = _("Could not communicate with the spell-checker program.");
380 // I think we have to check if ispell is still alive here because
381 // the signal-handler could have disabled blocking on the fd
398 case '#': // Not found, no near misses and guesses
401 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
402 case '&': // Not found, but we have near misses
405 char * p = strpbrk(buf, ":");
406 str = new char[strlen(p) + 1];
411 default: // This shouldn't happen, but you know Murphy
417 /* wait for ispell to finish */
425 void ISpell::insert(WordLangTuple const & word)
427 ::fputc('*', out); // Insert word in personal dictionary
428 ::fputs(word.word().c_str(), out);
433 void ISpell::accept(WordLangTuple const & word)
435 ::fputc('@', out); // Accept in this session
436 ::fputs(word.word().c_str(), out);
441 string const ISpell::error()