]> git.lyx.org Git - lyx.git/blobdiff - src/ispell.C
fix arabtex-related problems (bug 1225 and bug 1404)
[lyx.git] / src / ispell.C
index 4159334427f30458c786275783c93fce72f56045..6a2aac96b497963bcf9fbe71ee6630f8ee906baa 100644 (file)
@@ -1,81 +1,60 @@
 /**
  * \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 <levon@movementarian.org>
+ * \author Angus Leeming
+ * \author John Levon
+ *
+ * Full author contact details are available in file CREDITS.
  */
 
 #include <config.h>
 
-#ifdef __GNUG__
-#pragma implementation
-#endif
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <cstdio>
-
-// FIXME: do we need any of this horrible gook ? 
-#if TIME_WITH_SYS_TIME
-# include <sys/time.h>
-# include <ctime>
-#else
-# if HAVE_SYS_TIME_H
-#  include <sys/time.h>
-# else
-#  include <ctime>
-# endif
-#endif
-
-#ifdef HAVE_SYS_SELECT_H
-# ifdef HAVE_STRINGS_H
-   // <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 <strings.h>
-#  endif
-# endif
-#include <sys/select.h>
-#endif
+#include "ispell.h"
 
-#include "LString.h"
-#include "lyxrc.h"
-#include "language.h"
+#include "bufferparams.h"
 #include "debug.h"
 #include "encoding.h"
-#include "ispell.h"
+#include "gettext.h"
+#include "language.h"
+#include "lyxrc.h"
 #include "WordLangTuple.h"
 
 #include "support/forkedcall.h"
-#include "support/lstrings.h"
+
+// HP-UX 11.x doesn't have this header
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/time.h>
+
+using boost::shared_ptr;
 
 #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;
+namespace {
 
-class LaunchIspell : public ForkedProcess {
+class LaunchIspell : public lyx::support::ForkedProcess {
+       typedef lyx::support::ForkedProcess ForkedProcess;
 public:
        ///
        LaunchIspell(BufferParams const & p, string const & l,
-                    int * in, int * out)
-               : params(p), lang(l), pipein(in), pipeout(out) {}
+                    int * in, int * out, int * err)
+               : params(p), lang(l), pipein(in), pipeout(out), pipeerr(err) {}
        ///
-       virtual ForkedProcess * clone() const { 
-               return new LaunchIspell(*this);
+       virtual shared_ptr<ForkedProcess> clone() const {
+               return shared_ptr<ForkedProcess>(new LaunchIspell(*this));
        }
        ///
        int start();
@@ -88,19 +67,20 @@ private:
        string const & lang;
        int * const pipein;
        int * const pipeout;
+       int * const pipeerr;
 };
 
 
 int LaunchIspell::start()
 {
-       command_ = "ispell";
-       return runNonBlocking();
+       command_ = lyxrc.isp_command;
+       return run(DontWait);
 }
 
 
 int LaunchIspell::generateChild()
-{      
-       isp_pid = fork();
+{
+       pid_t isp_pid = fork();
 
        if (isp_pid != 0) {
                // failed (-1) or parent process (>0)
@@ -110,10 +90,13 @@ int LaunchIspell::generateChild()
        // child process
        dup2(pipein[0], STDIN_FILENO);
        dup2(pipeout[1], STDOUT_FILENO);
-       ::close(pipein[0]);
-       ::close(pipein[1]);
-       ::close(pipeout[0]);
-       ::close(pipeout[1]);
+       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 = 0;
@@ -154,7 +137,7 @@ int LaunchIspell::generateChild()
                string("-w").copy(tmp, 2); tmp[2] = '\0';
                argv[argc++] = tmp;
                // Put the escape chars in ""s
-               string tms = "\"" + lyxrc.isp_esc_chars + "\"";
+               string tms = '"' + lyxrc.isp_esc_chars + '"';
                tmp = new char[tms.length() + 1];
                tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
                argv[argc++] = tmp;
@@ -204,122 +187,161 @@ int LaunchIspell::generateChild()
 
 
 ISpell::ISpell(BufferParams const & params, string const & lang)
-       : str(0)
+       : in(0), out(0), inerr(0), str(0)
 {
-       static char o_buf[BUFSIZ];  // jc: it could be smaller
-       int pipein[2];
-       int pipeout[2];
+       lyxerr[Debug::GUI] << "Created ispell" << endl;
 
-       isp_pid = -1;
+       // static due to the setvbuf. Ugly.
+       static char o_buf[BUFSIZ];
 
-       if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
-               lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
-               setError();
+       // We need to throw an exception not do this
+       pipein[0] = pipein[1] = pipeout[0] = pipeout[1]
+               = pipeerr[0] = pipeerr[1] = -1;
+
+       // This is what happens when goto gets banned.
+
+       if (pipe(pipein) == -1) {
+               error_ = _("Can't create pipe for spellchecker.");
+               return;
+       }
+
+       if (pipe(pipeout) == -1) {
+               close(pipein[0]);
+               close(pipein[1]);
+               error_ = _("Can't create pipe for spellchecker.");
+               return;
+       }
+
+       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 ((out = fdopen(pipein[1], "w")) == 0) {
-               lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
-                      << endl;
-               setError();
+               error_ = _("Can't open pipe for spellchecker.");
                return;
        }
 
        if ((in = fdopen(pipeout[0], "r")) == 0) {
-               lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
-                      << endl;
-               setError();
+               error_ = _("Can't open pipe for spellchecker.");
                return;
        }
 
-       setvbuf(out, o_buf, _IOLBF, BUFSIZ);
+       if ((inerr = fdopen(pipeerr[0], "r")) == 0) {
+               error_ = _("Can't open pipe for spellchecker.");
+               return;
+       }
 
-       isp_fd = pipeout[0];
+       setvbuf(out, o_buf, _IOLBF, BUFSIZ);
 
-       LaunchIspell childprocess(params, lang, pipein, pipeout);
-       isp_pid = childprocess.start();
-       if (isp_pid == -1) {
-               lyxerr << "LyX: Can't create child process for spellchecker!"
-                      << endl;
-               setError();
+       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;
        }
 
-       setError();
        /* 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));
+       bool err_read;
+       bool error = select(err_read);
+
+       if (!error) {
+               if (!err_read) {
+                       // Set terse mode (silently accept correct words)
+                       fputs("!\n", out);
+                       return;
+               }
 
-       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;
+               /* must have read something from stderr */
+               error_ = buf;
        } else {
-               // Select returned error
-               lyxerr << "Select on ispell returned error, what now?" << endl;
+               // 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;
 }
 
 
-void ISpell::setError()
+bool ISpell::select(bool & err_read)
 {
-       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";
-       } else {
-               error_ = 0;
+       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;
        }
+
+       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;
@@ -333,13 +355,7 @@ string const ISpell::nextMiss()
 
 bool ISpell::alive()
 {
-       return isp_pid != -1;
-}
-
-
-void ISpell::cleanUp()
-{
-       ::fclose(out);
+       return child_.get() && child_->running();
 }
 
 
@@ -348,12 +364,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
@@ -400,20 +426,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
@@ -432,7 +444,5 @@ void ISpell::accept(WordLangTuple const & word)
 
 string const ISpell::error()
 {
-       if (error_)
-               return error_;
-       return "";
+       return error_;
 }