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"
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>
33 #ifdef HAVE_SYS_TIME_H
34 # include <sys/time.h>
37 using boost::shared_ptr;
39 #ifndef CXX_GLOBAL_CSTD
52 using support::bformat;
56 class LaunchIspell : public support::ForkedProcess {
57 typedef support::ForkedProcess ForkedProcess;
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) {}
64 virtual shared_ptr<ForkedProcess> clone() const {
65 return shared_ptr<ForkedProcess>(new LaunchIspell(*this));
71 virtual int generateChild();
74 BufferParams const & params;
82 int LaunchIspell::start()
84 command_ = lyxrc.isp_command;
89 int LaunchIspell::generateChild()
91 pid_t isp_pid = fork();
94 // failed (-1) or parent process (>0)
99 dup2(pipein[0], STDIN_FILENO);
100 dup2(pipeout[1], STDOUT_FILENO);
101 dup2(pipeerr[1], STDERR_FILENO);
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';
117 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
120 if (lang != "default") {
122 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
124 tmp = new char[lang.length() + 1];
125 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
129 if (lyxrc.isp_accept_compound) {
130 // Consider run-together words as legal compounds
132 string("-C").copy(tmp, 2); tmp[2] = '\0';
135 // Report run-together words with
136 // missing blanks as errors
138 string("-B").copy(tmp, 2); tmp[2] = '\0';
141 if (lyxrc.isp_use_esc_chars) {
142 // Specify additional characters that
143 // can be part of a word
145 string("-w").copy(tmp, 2); tmp[2] = '\0';
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';
153 if (lyxrc.isp_use_pers_dict) {
154 // Specify an alternate personal dictionary
156 string("-p").copy(tmp, 2);
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';
164 if (lyxrc.isp_use_input_encoding &&
165 params.inputenc != "default") {
166 string enc = (params.inputenc == "auto")
167 ? params.language->encoding()->latexName()
169 size_t const n = enc.length();
171 string("-T").copy(tmp, 2);
173 argv[argc++] = tmp; // Input encoding
174 tmp = new char[n + 1];
182 execvp(argv[0], const_cast<char * const *>(argv));
184 // free the memory used by string::copy in the
186 for (int i = 0; i < argc - 1; ++i)
189 lyxerr << "LyX: Failed to start ispell!" << endl;
194 string const to_iconv_encoding(docstring const & s, string const & encoding)
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());
201 // FIXME UNICODE: we don't need to convert to UTF8, but probably to the locale encoding
206 docstring const from_iconv_encoding(string const & s, string const & encoding)
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());
213 // FIXME UNICODE: s is not in UTF8, but probably the locale encoding
220 ISpell::ISpell(BufferParams const & params, string const & lang)
221 : in(0), out(0), inerr(0), str(0)
223 //LYXERR(Debug::GUI) << "Created ispell" << endl;
225 encoding = params.encoding().iconvName();
227 // static due to the setvbuf. Ugly.
228 static char o_buf[BUFSIZ];
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;
234 // This is what happens when goto gets banned.
236 if (pipe(pipein) == -1) {
237 error_ = _("Can't create pipe for spellchecker.");
241 if (pipe(pipeout) == -1) {
244 error_ = _("Can't create pipe for spellchecker.");
248 if (pipe(pipeerr) == -1) {
253 error_ = _("Can't create pipe for spellchecker.");
257 if ((out = fdopen(pipein[1], "w")) == 0) {
258 error_ = _("Can't open pipe for spellchecker.");
262 if ((in = fdopen(pipeout[0], "r")) == 0) {
263 error_ = _("Can't open pipe for spellchecker.");
267 if ((inerr = fdopen(pipeerr[0], "r")) == 0) {
268 error_ = _("Can't open pipe for spellchecker.");
272 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
274 LaunchIspell * li = new LaunchIspell(params, lang, pipein, pipeout, pipeerr);
276 if (li->start() == -1) {
277 error_ = _("Could not create an ispell process.\nYou may not have "
278 "the right languages installed.");
283 // Parent process: Read ispells identification message
286 bool error = select(err_read);
290 // Set terse mode (silently accept correct words)
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);
299 // select returned error
300 error_ = _("The ispell process returned an error.\nPerhaps "
301 "it has been configured wrongly ?");
317 //LYXERR(Debug::GUI) << "Killing ispell" << endl;
326 fputs("#\n", out); // Save personal dictionary
342 bool ISpell::select(bool & err_read)
348 FD_SET(pipeout[0], &infds);
349 FD_SET(pipeerr[0], &infds);
353 retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[0]) + 1),
354 SELECT_TYPE_ARG234 (&infds),
357 SELECT_TYPE_ARG5 (&tv));
363 if (FD_ISSET(pipeerr[0], &infds)) {
364 fgets(buf, BUFSIZ, inerr);
369 fgets(buf, BUFSIZ, in);
375 docstring const ISpell::nextMiss()
377 // Well, somebody is a sick fuck.
379 if (str == 0 || *(e+1) == '\0')
382 e = strpbrk(b, ",\n");
385 return from_iconv_encoding(b, encoding);
392 return child_.get() && child_->running();
396 enum ISpell::Result ISpell::check(WordLangTuple const & word)
398 // FIXME Please rewrite to use string.
402 string const encoded = to_iconv_encoding(word.word(), encoding);
403 if (encoded.empty()) {
405 _("Could not check word `%1$s' because it could not be converted to encoding `%2$s'."),
406 word.word(), from_ascii(encoding));
409 ::fputs(encoded.c_str(), out);
413 bool error = select(err_read);
416 error_ = _("Could not communicate with the ispell spellchecker process.");
421 // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
422 error_ = from_utf8(buf);
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
444 case '#': // Not found, no near misses and guesses
447 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
448 case '&': // Not found, but we have near misses
450 res = SUGGESTED_WORDS;
451 char * p = strpbrk(buf, ":");
452 str = new char[strlen(p) + 1];
457 default: // This shouldn't happen, but you know Murphy
462 if (res != IGNORED_WORD) {
463 /* wait for ispell to finish */
471 void ISpell::insert(WordLangTuple const & word)
473 string const encoded = to_iconv_encoding(word.word(), encoding);
474 if (encoded.empty()) {
476 _("Could not insert word `%1$s' because it could not be converted to encoding `%2$s'."),
477 word.word(), from_ascii(encoding));
480 ::fputc('*', out); // Insert word in personal dictionary
481 ::fputs(encoded.c_str(), out);
486 void ISpell::accept(WordLangTuple const & word)
488 string const encoded = to_iconv_encoding(word.word(), encoding);
489 if (encoded.empty()) {
491 _("Could not accept word `%1$s' because it could not be converted to encoding `%2$s'."),
492 word.word(), from_ascii(encoding));
495 ::fputc('@', out); // Accept in this session
496 ::fputs(encoded.c_str(), out);
501 docstring const ISpell::error()