X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fispell.C;h=f1abc99bf013fc419307554498857b245ad9f765;hb=78046794ccfce3a20751e00b35295c290853afd6;hp=e531795dbe41c08c0e03369221bd8b3c583a86f4;hpb=c10dfd15cd79499dd23f161d8f1716c00a143e09;p=lyx.git diff --git a/src/ispell.C b/src/ispell.C index e531795dbe..f1abc99bf0 100644 --- a/src/ispell.C +++ b/src/ispell.C @@ -1,286 +1,344 @@ /** * \file ispell.C - * Copyright 2002 the LyX Team - * Read the file COPYING + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. * * \author unknown - * \author John Levon + * \author Angus Leeming + * \author John Levon + * + * Full author contact details are available in file CREDITS. */ #include -#ifdef __GNUG__ -#pragma implementation -#endif +#include "ispell.h" -#include -#include -#include -#include -#include - -// FIXME: do we need any of this horrible gook ? -#if TIME_WITH_SYS_TIME -# include -# include -#else -# if HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif +#include "bufferparams.h" +#include "debug.h" +#include "encoding.h" +#include "gettext.h" +#include "language.h" +#include "lyxrc.h" +#include "WordLangTuple.h" + +#include "support/forkedcall.h" +// HP-UX 11.x doesn't have this header #ifdef HAVE_SYS_SELECT_H -# ifdef HAVE_STRINGS_H - // is needed at least on AIX because FD_ZERO uses bzero(). - // BUT we cannot include both string.h and strings.h on Irix 6.5 :( -# ifdef _AIX -# include -# endif -# endif #include #endif - -#include "LString.h" -#include "support/lstrings.h" -#include "lyxrc.h" -#include "language.h" -#include "debug.h" -#include "encoding.h" -#include "ispell.h" -#include "WordLangTuple.h" +#include #ifndef CXX_GLOBAL_CSTD using std::strcpy; using std::strlen; using std::strpbrk; -using std::strstr; #endif using std::endl; +using std::max; +using std::string; + namespace { - /// pid for the `ispell' process. - pid_t isp_pid = -1; + +class LaunchIspell : public lyx::support::ForkedProcess { +public: + /// + LaunchIspell(BufferParams const & p, string const & l, + int * in, int * out, int * err) + : params(p), lang(l), pipein(in), pipeout(out), pipeerr(err) {} + /// + virtual lyx::support::ForkedProcess * clone() const { + return new LaunchIspell(*this); + } + /// + int start(); +private: + /// + virtual int generateChild(); + + /// + BufferParams const & params; + string const & lang; + int * const pipein; + int * const pipeout; + int * const pipeerr; +}; + + +int LaunchIspell::start() +{ + command_ = lyxrc.isp_command; + return runNonBlocking(); } -ISpell::ISpell(BufferParams const & params, string const & lang) - : str(0) +int LaunchIspell::generateChild() { - static char o_buf[BUFSIZ]; // jc: it could be smaller - int pipein[2]; - int pipeout[2]; + pid_t isp_pid = fork(); + + if (isp_pid != 0) { + // failed (-1) or parent process (>0) + return isp_pid; + } + + // child process + dup2(pipein[0], STDIN_FILENO); + dup2(pipeout[1], STDOUT_FILENO); + dup2(pipeerr[1], STDERR_FILENO); + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + close(pipeerr[0]); + close(pipeerr[1]); + char * argv[14]; - int argc; + int argc = 0; - isp_pid = -1; + char * tmp = new char[lyxrc.isp_command.length() + 1]; + lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length()); + tmp[lyxrc.isp_command.length()] = '\0'; + argv[argc++] = tmp; + tmp = new char[3]; + string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode + argv[argc++] = tmp; - if (pipe(pipein) == -1 || pipe(pipeout) == -1) { - lyxerr << "LyX: Can't create pipe for spellchecker!" << endl; - goto END; + if (lang != "default") { + tmp = new char[3]; + string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file + argv[argc++] = tmp; + tmp = new char[lang.length() + 1]; + lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0'; + argv[argc++] = tmp; } - if ((out = fdopen(pipein[1], "w")) == 0) { - lyxerr << "LyX: Can't create stream for pipe for spellchecker!" - << endl; - goto END; + if (lyxrc.isp_accept_compound) { + // Consider run-together words as legal compounds + tmp = new char[3]; + string("-C").copy(tmp, 2); tmp[2] = '\0'; + argv[argc++] = tmp; + } else { + // Report run-together words with + // missing blanks as errors + tmp = new char[3]; + string("-B").copy(tmp, 2); tmp[2] = '\0'; + argv[argc++] = tmp; } - - if ((in = fdopen(pipeout[0], "r")) == 0) { - lyxerr <<"LyX: Can't create stream for pipe for spellchecker!" - << endl; - goto END; + if (lyxrc.isp_use_esc_chars) { + // Specify additional characters that + // can be part of a word + tmp = new char[3]; + string("-w").copy(tmp, 2); tmp[2] = '\0'; + argv[argc++] = tmp; + // Put the escape chars in ""s + string tms = '"' + lyxrc.isp_esc_chars + '"'; + tmp = new char[tms.length() + 1]; + tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0'; + argv[argc++] = tmp; + } + if (lyxrc.isp_use_pers_dict) { + // Specify an alternate personal dictionary + tmp = new char[3]; + string("-p").copy(tmp, 2); + tmp[2]= '\0'; + argv[argc++] = tmp; + tmp = new char[lyxrc.isp_pers_dict.length() + 1]; + lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length()); + tmp[lyxrc.isp_pers_dict.length()] = '\0'; + argv[argc++] = tmp; + } + if (lyxrc.isp_use_input_encoding && + params.inputenc != "default") { + string enc = (params.inputenc == "auto") + ? params.language->encoding()->LatexName() + : params.inputenc; + string::size_type n = enc.length(); + tmp = new char[3]; + string("-T").copy(tmp, 2); + tmp[2] = '\0'; + argv[argc++] = tmp; // Input encoding + tmp = new char[n + 1]; + enc.copy(tmp, n); + tmp[n] = '\0'; + argv[argc++] = tmp; } - setvbuf(out, o_buf, _IOLBF, BUFSIZ); + argv[argc++] = 0; + + execvp(argv[0], const_cast(argv)); + + // free the memory used by string::copy in the + // setup of argv + for (int i = 0; i < argc - 1; ++i) + delete[] argv[i]; + + lyxerr << "LyX: Failed to start ispell!" << endl; + _exit(0); +} + + +} // namespace anon + + +ISpell::ISpell(BufferParams const & params, string const & lang) + : in(0), out(0), inerr(0), str(0) +{ + lyxerr[Debug::GUI] << "Created ispell" << endl; + + // static due to the setvbuf. Ugly. + static char o_buf[BUFSIZ]; - isp_fd = pipeout[0]; + // We need to throw an exception not do this + pipein[0] = pipein[1] = pipeout[0] = pipeout[1] + = pipeerr[0] = pipeerr[1] = -1; - isp_pid = fork(); + // This is what happens when goto gets banned. - if (isp_pid == -1) { - lyxerr << "LyX: Can't create child process for spellchecker!" - << endl; - goto END; + if (pipe(pipein) == -1) { + error_ = _("Can't create pipe for spellchecker."); + return; } - if (isp_pid == 0) { - /* child process */ - dup2(pipein[0], STDIN_FILENO); - dup2(pipeout[1], STDOUT_FILENO); - ::close(pipein[0]); - ::close(pipein[1]); - ::close(pipeout[0]); - ::close(pipeout[1]); - - argc = 0; - char * tmp = new char[lyxrc.isp_command.length() + 1]; - lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length()); - tmp[lyxrc.isp_command.length()] = '\0'; - argv[argc++] = tmp; - tmp = new char[3]; - string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode - argv[argc++] = tmp; + if (pipe(pipeout) == -1) { + close(pipein[0]); + close(pipein[1]); + error_ = _("Can't create pipe for spellchecker."); + return; + } - if (lang != "default") { - tmp = new char[3]; - string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file - argv[argc++] = tmp; - tmp = new char[lang.length() + 1]; - lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0'; - argv[argc++] = tmp; - } + if (pipe(pipeerr) == -1) { + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + error_ = _("Can't create pipe for spellchecker."); + return; + } - if (lyxrc.isp_accept_compound) { - // Consider run-together words as legal compounds - tmp = new char[3]; - string("-C").copy(tmp, 2); tmp[2] = '\0'; - argv[argc++] = tmp; - } else { - // Report run-together words with - // missing blanks as errors - tmp = new char[3]; - string("-B").copy(tmp, 2); tmp[2] = '\0'; - argv[argc++] = tmp; - } - if (lyxrc.isp_use_esc_chars) { - // Specify additional characters that - // can be part of a word - tmp = new char[3]; - string("-w").copy(tmp, 2); tmp[2] = '\0'; - argv[argc++] = tmp; - // Put the escape chars in ""s - string tms = "\"" + lyxrc.isp_esc_chars + "\""; - tmp = new char[tms.length() + 1]; - tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0'; - argv[argc++] = tmp; - } - if (lyxrc.isp_use_pers_dict) { - // Specify an alternate personal dictionary - tmp = new char[3]; - string("-p").copy(tmp, 2); - tmp[2]= '\0'; - argv[argc++] = tmp; - tmp = new char[lyxrc.isp_pers_dict.length() + 1]; - lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length()); - tmp[lyxrc.isp_pers_dict.length()] = '\0'; - argv[argc++] = tmp; - } - if (lyxrc.isp_use_input_encoding && - params.inputenc != "default") { - string enc = (params.inputenc == "auto") - ? params.language->encoding()->LatexName() - : params.inputenc; - string::size_type n = enc.length(); - tmp = new char[3]; - string("-T").copy(tmp, 2); - tmp[2] = '\0'; - argv[argc++] = tmp; // Input encoding - tmp = new char[n + 1]; - enc.copy(tmp, n); - tmp[n] = '\0'; - argv[argc++] = tmp; - } + if ((out = fdopen(pipein[1], "w")) == 0) { + error_ = _("Can't open pipe for spellchecker."); + return; + } - argv[argc++] = 0; + if ((in = fdopen(pipeout[0], "r")) == 0) { + error_ = _("Can't open pipe for spellchecker."); + return; + } - execvp(argv[0], const_cast(argv)); + if ((inerr = fdopen(pipeerr[0], "r")) == 0) { + error_ = _("Can't open pipe for spellchecker."); + return; + } - // free the memory used by string::copy in the - // setup of argv - for (int i = 0; i < argc - 1; ++i) - delete[] argv[i]; + setvbuf(out, o_buf, _IOLBF, BUFSIZ); - lyxerr << "LyX: Failed to start ispell!" << endl; - _exit(0); + LaunchIspell * li = new LaunchIspell(params, lang, pipein, pipeout, pipeerr); + child_.reset(li); + if (li->start() == -1) { + error_ = _("Could not create an ispell process.\nYou may not have " + "the right languages installed."); + child_.reset(0); + return; } - { - /* Parent process: Read ispells identification message */ - // Hmm...what are we using this id msg for? Nothing? (Lgb) - // Actually I used it to tell if it's truly Ispell or if it's - // aspell -- (kevinatk@home.com) - // But no code actually used the results for anything useful - // so I removed it again. Perhaps we can remove this code too. - // - jbl - char buf[2048]; - fd_set infds; - struct timeval tv; - int retval = 0; - FD_ZERO(&infds); - FD_SET(pipeout[0], &infds); - tv.tv_sec = 15; // fifteen second timeout. Probably too much, - // but it can't really hurt. - tv.tv_usec = 0; - - // Configure provides us with macros which are supposed to do - // the right typecast. - retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1), - SELECT_TYPE_ARG234 (&infds), - 0, - 0, - SELECT_TYPE_ARG5 (&tv)); - - if (retval > 0) { - // Ok, do the reading. We don't have to FD_ISSET since - // there is only one fd in infds. - fgets(buf, 2048, in); - - fputs("!\n", out); // Set terse mode (silently accept correct words) - - } else if (retval == 0) { - // timeout. Give nice message to user. - lyxerr << "Ispell read timed out, what now?" << endl; - // This probably works but could need some thought - isp_pid = -1; - ::close(pipeout[0]); - ::close(pipeout[1]); - ::close(pipein[0]); - ::close(pipein[1]); - isp_fd = -1; - } else { - // Select returned error - lyxerr << "Select on ispell returned error, what now?" << endl; + + /* Parent process: Read ispells identification message */ + + bool err_read; + bool error = select(err_read); + + if (!error) { + if (!err_read) { + // Set terse mode (silently accept correct words) + fputs("!\n", out); + return; } - } - END: - if (isp_pid == -1) { - error_ = - "\n\n" - "The spellcheck-process has died for some reason.\n" - "*One* possible reason could be that you do not have\n" - "a dictionary file for the language of this document\n" - "installed.\n" - "Check your spellchecker or set another dictionary\n" - "in the Spellchecker Options menu.\n\n"; + + /* must have read something from stderr */ + error_ = buf; } else { - error_ = 0; + // select returned error + error_ = _("The spell process returned an error.\nPerhaps " + "it has been configured wrongly ?"); } + + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + close(pipeerr[0]); + close(pipeerr[1]); + child_->kill(); + child_.reset(0); } ISpell::~ISpell() { - delete[] str; + lyxerr[Debug::GUI] << "Killing ispell" << endl; + + if (in) + fclose(in); + + if (inerr) + fclose(inerr); + + if (out) { + fputs("#\n", out); // Save personal dictionary + + fflush(out); + fclose(out); + } + + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + close(pipeerr[0]); + close(pipeerr[1]); + delete [] str; } -/* FIXME: this is a minimalist solution until the above - * code is able to work with forkedcall.h. We only need - * to reap the zombies here. - */ -void reapSpellchecker(void) +bool ISpell::select(bool & err_read) { - if (isp_pid == -1) - return; + fd_set infds; + struct timeval tv; + int retval = 0; + FD_ZERO(&infds); + FD_SET(pipeout[0], &infds); + FD_SET(pipeerr[0], &infds); + tv.tv_sec = 2; + tv.tv_usec = 0; + + retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[0]) + 1), + SELECT_TYPE_ARG234 (&infds), + 0, + 0, + SELECT_TYPE_ARG5 (&tv)); + + // error + if (retval <= 0) + return true; + + if (FD_ISSET(pipeerr[0], &infds)) { + fgets(buf, BUFSIZ, inerr); + err_read = true; + return false; + } - waitpid(isp_pid, 0, WNOHANG); + fgets(buf, BUFSIZ, in); + err_read = false; + return false; } string const ISpell::nextMiss() { + // Well, somebody is a sick fuck. + if (str == 0 || *(e+1) == '\0') return ""; char * b = e + 2; @@ -294,13 +352,7 @@ string const ISpell::nextMiss() bool ISpell::alive() { - return isp_pid != -1; -} - - -void ISpell::cleanUp() -{ - ::fclose(out); + return child_.get() && child_->running(); } @@ -309,12 +361,22 @@ enum ISpell::Result ISpell::check(WordLangTuple const & word) // FIXME Please rewrite to use string. Result res; - + ::fputs(word.word().c_str(), out); ::fputc('\n', out); - char buf[1024]; - ::fgets(buf, 1024, in); + bool err_read; + bool error = select(err_read); + + if (error) { + error_ = _("Could not communicate with the spell-checker program."); + return UNKNOWN; + } + + if (err_read) { + error_ = buf; + return UNKNOWN; + } // I think we have to check if ispell is still alive here because // the signal-handler could have disabled blocking on the fd @@ -361,20 +423,6 @@ enum ISpell::Result ISpell::check(WordLangTuple const & word) } -void ISpell::close() -{ - // Note: If you decide to optimize this out when it is not - // needed please note that when Aspell is used this command - // is also needed to save the replacement dictionary. - // -- Kevin Atkinson (kevinatk@home.com) - - fputs("#\n", out); // Save personal dictionary - - fflush(out); - fclose(out); -} - - void ISpell::insert(WordLangTuple const & word) { ::fputc('*', out); // Insert word in personal dictionary @@ -393,7 +441,5 @@ void ISpell::accept(WordLangTuple const & word) string const ISpell::error() { - if (error_) - return error_; - return ""; + return error_; }