3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Angus Leeming
10 * Full author contact details are available in file CREDITS.
17 #include "bufferparams.h"
23 #include "WordLangTuple.h"
25 #include "support/forkedcall.h"
27 // HP-UX 11.x doesn't have this header
28 #ifdef HAVE_SYS_SELECT_H
29 # include <sys/select.h>
31 #ifdef HAVE_SYS_TIME_H
32 # include <sys/time.h>
38 using boost::shared_ptr;
40 #ifndef CXX_GLOBAL_CSTD
53 class LaunchIspell : public support::ForkedProcess {
54 typedef support::ForkedProcess ForkedProcess;
57 LaunchIspell(BufferParams const & p, string const & l,
58 int * in, int * out, int * err)
59 : params(p), lang(l), pipein(in), pipeout(out), pipeerr(err) {}
61 virtual shared_ptr<ForkedProcess> clone() const {
62 return shared_ptr<ForkedProcess>(new LaunchIspell(*this));
68 virtual int generateChild();
71 BufferParams const & params;
79 int LaunchIspell::start()
81 command_ = lyxrc.isp_command;
86 int LaunchIspell::generateChild()
88 pid_t isp_pid = fork();
91 // failed (-1) or parent process (>0)
96 dup2(pipein[0], STDIN_FILENO);
97 dup2(pipeout[1], STDOUT_FILENO);
98 dup2(pipeerr[1], STDERR_FILENO);
109 char * tmp = new char[lyxrc.isp_command.length() + 1];
110 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
111 tmp[lyxrc.isp_command.length()] = '\0';
114 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
117 if (lang != "default") {
119 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
121 tmp = new char[lang.length() + 1];
122 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
126 if (lyxrc.isp_accept_compound) {
127 // Consider run-together words as legal compounds
129 string("-C").copy(tmp, 2); tmp[2] = '\0';
132 // Report run-together words with
133 // missing blanks as errors
135 string("-B").copy(tmp, 2); tmp[2] = '\0';
138 if (lyxrc.isp_use_esc_chars) {
139 // Specify additional characters that
140 // can be part of a word
142 string("-w").copy(tmp, 2); tmp[2] = '\0';
144 // Put the escape chars in ""s
145 string tms = '"' + lyxrc.isp_esc_chars + '"';
146 tmp = new char[tms.length() + 1];
147 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
150 if (lyxrc.isp_use_pers_dict) {
151 // Specify an alternate personal dictionary
153 string("-p").copy(tmp, 2);
156 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
157 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
158 tmp[lyxrc.isp_pers_dict.length()] = '\0';
161 if (lyxrc.isp_use_input_encoding &&
162 params.inputenc != "default") {
163 string enc = (params.inputenc == "auto")
164 ? params.language->encoding()->latexName()
166 size_t const n = enc.length();
168 string("-T").copy(tmp, 2);
170 argv[argc++] = tmp; // Input encoding
171 tmp = new char[n + 1];
179 execvp(argv[0], const_cast<char * const *>(argv));
181 // free the memory used by string::copy in the
183 for (int i = 0; i < argc - 1; ++i)
186 lyxerr << "LyX: Failed to start ispell!" << endl;
194 ISpell::ISpell(BufferParams const & params, string const & lang)
195 : in(0), out(0), inerr(0), str(0)
197 lyxerr[Debug::GUI] << "Created ispell" << endl;
199 // static due to the setvbuf. Ugly.
200 static char o_buf[BUFSIZ];
202 // We need to throw an exception not do this
203 pipein[0] = pipein[1] = pipeout[0] = pipeout[1]
204 = pipeerr[0] = pipeerr[1] = -1;
206 // This is what happens when goto gets banned.
208 if (pipe(pipein) == -1) {
209 error_ = _("Can't create pipe for spellchecker.");
213 if (pipe(pipeout) == -1) {
216 error_ = _("Can't create pipe for spellchecker.");
220 if (pipe(pipeerr) == -1) {
225 error_ = _("Can't create pipe for spellchecker.");
229 if ((out = fdopen(pipein[1], "w")) == 0) {
230 error_ = _("Can't open pipe for spellchecker.");
234 if ((in = fdopen(pipeout[0], "r")) == 0) {
235 error_ = _("Can't open pipe for spellchecker.");
239 if ((inerr = fdopen(pipeerr[0], "r")) == 0) {
240 error_ = _("Can't open pipe for spellchecker.");
244 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
246 LaunchIspell * li = new LaunchIspell(params, lang, pipein, pipeout, pipeerr);
248 if (li->start() == -1) {
249 error_ = _("Could not create an ispell process.\nYou may not have "
250 "the right languages installed.");
255 // Parent process: Read ispells identification message
258 bool error = select(err_read);
262 // Set terse mode (silently accept correct words)
267 // must have read something from stderr
268 // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
269 error_ =from_utf8(buf);
271 // select returned error
272 error_ = _("The ispell process returned an error.\nPerhaps "
273 "it has been configured wrongly ?");
289 lyxerr[Debug::GUI] << "Killing ispell" << endl;
298 fputs("#\n", out); // Save personal dictionary
314 bool ISpell::select(bool & err_read)
320 FD_SET(pipeout[0], &infds);
321 FD_SET(pipeerr[0], &infds);
325 retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[0]) + 1),
326 SELECT_TYPE_ARG234 (&infds),
329 SELECT_TYPE_ARG5 (&tv));
335 if (FD_ISSET(pipeerr[0], &infds)) {
336 fgets(buf, BUFSIZ, inerr);
341 fgets(buf, BUFSIZ, in);
347 docstring const ISpell::nextMiss()
349 // Well, somebody is a sick fuck.
351 if (str == 0 || *(e+1) == '\0')
354 e = strpbrk(b, ",\n");
357 // FIXME UNICODE: b is not in UTF8, but probably the locale encoding
365 return child_.get() && child_->running();
369 enum ISpell::Result ISpell::check(WordLangTuple const & word)
371 // FIXME Please rewrite to use string.
375 // FIXME UNICODE: we don't need to convert to UTF8, but probably to the locale encoding
376 ::fputs(to_utf8(word.word()).c_str(), out);
380 bool error = select(err_read);
383 error_ = _("Could not communicate with the ispell spellchecker process.");
388 // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
389 error_ = from_utf8(buf);
393 // I think we have to check if ispell is still alive here because
394 // the signal-handler could have disabled blocking on the fd
411 case '#': // Not found, no near misses and guesses
414 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
415 case '&': // Not found, but we have near misses
417 res = SUGGESTED_WORDS;
418 char * p = strpbrk(buf, ":");
419 str = new char[strlen(p) + 1];
424 default: // This shouldn't happen, but you know Murphy
429 if (res != IGNORED_WORD) {
430 /* wait for ispell to finish */
438 void ISpell::insert(WordLangTuple const & word)
440 ::fputc('*', out); // Insert word in personal dictionary
441 // FIXME UNICODE: we don't need to convert to UTF8, but probably to the locale encoding
442 ::fputs(to_utf8(word.word()).c_str(), out);
447 void ISpell::accept(WordLangTuple const & word)
449 ::fputc('@', out); // Accept in this session
450 // FIXME UNICODE: we don't need to convert to UTF8, but probably to the locale encoding
451 ::fputs(to_utf8(word.word()).c_str(), out);
456 docstring const ISpell::error()