/**
* \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>
-#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"
+#include "support/unicode.h"
+
+// HP-UX 11.x doesn't have this header
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+
+namespace lyx {
+
+using support::bformat;
+
+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 support::ForkedProcess {
+ typedef 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();
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)
// 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;
if (lyxrc.isp_use_input_encoding &&
params.inputenc != "default") {
string enc = (params.inputenc == "auto")
- ? params.language->encoding()->LatexName()
+ ? params.language->encoding()->latexName()
: params.inputenc;
- string::size_type n = enc.length();
+ size_t const n = enc.length();
tmp = new char[3];
string("-T").copy(tmp, 2);
tmp[2] = '\0';
}
+string const to_iconv_encoding(docstring const & s, string const & encoding)
+{
+ if (lyxrc.isp_use_input_encoding) {
+ std::vector<char> const encoded =
+ ucs4_to_eightbit(s.data(), s.length(), encoding);
+ return string(encoded.begin(), encoded.end());
+ }
+ // FIXME UNICODE: we don't need to convert to UTF8, but probably to the locale encoding
+ return to_utf8(s);
+}
+
+
+docstring const from_iconv_encoding(string const & s, string const & encoding)
+{
+ if (lyxrc.isp_use_input_encoding) {
+ std::vector<char_type> const ucs4 =
+ eightbit_to_ucs4(s.data(), s.length(), encoding);
+ return docstring(ucs4.begin(), ucs4.end());
+ }
+ // FIXME UNICODE: s is not in UTF8, but probably the locale encoding
+ return from_utf8(s);
+}
+
} // namespace anon
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;
+
+ encoding = params.encoding().iconvName();
+
+ // static due to the setvbuf. Ugly.
+ static char o_buf[BUFSIZ];
- isp_pid = -1;
+ // We need to throw an exception not do this
+ pipein[0] = pipein[1] = pipeout[0] = pipeout[1]
+ = pipeerr[0] = pipeerr[1] = -1;
- if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
- lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
- setError();
+ // 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];
+ // 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;
+ }
+
+ // must have read something from stderr
+ // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
+ error_ = from_utf8(buf);
+ } else {
+ // select returned error
+ error_ = _("The ispell 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()
+{
+ 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;
+}
+
+
+bool ISpell::select(bool & err_read)
+{
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.
+ FD_SET(pipeerr[0], &infds);
+ tv.tv_sec = 2;
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),
+ retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[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;
- }
-}
+ // error
+ if (retval <= 0)
+ return true;
+ if (FD_ISSET(pipeerr[0], &infds)) {
+ fgets(buf, BUFSIZ, inerr);
+ err_read = true;
+ return false;
+ }
-ISpell::~ISpell()
-{
- delete[] str;
+ fgets(buf, BUFSIZ, in);
+ err_read = false;
+ return false;
}
-void ISpell::setError()
+docstring const ISpell::nextMiss()
{
- 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;
- }
-}
-
+ // Well, somebody is a sick fuck.
-string const ISpell::nextMiss()
-{
if (str == 0 || *(e+1) == '\0')
- return "";
+ return docstring();
char * b = e + 2;
e = strpbrk(b, ",\n");
*e = '\0';
if (b)
- return b;
- return "";
+ return from_iconv_encoding(b, encoding);
+ return docstring();
}
bool ISpell::alive()
{
- return isp_pid != -1;
-}
-
-
-void ISpell::cleanUp()
-{
- ::fclose(out);
+ return child_.get() && child_->running();
}
Result res;
- ::fputs(word.word().c_str(), out);
+ string const encoded = to_iconv_encoding(word.word(), encoding);
+ if (encoded.empty()) {
+ error_ = bformat(
+ _("Could not check word `%1$s' because it could not be converted to encoding `%2$s'."),
+ word.word(), from_ascii(encoding));
+ return UNKNOWN_WORD;
+ }
+ ::fputs(encoded.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 ispell spellchecker process.");
+ return UNKNOWN_WORD;
+ }
+
+ if (err_read) {
+ // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
+ error_ = from_utf8(buf);
+ return UNKNOWN_WORD;
+ }
// I think we have to check if ispell is still alive here because
// the signal-handler could have disabled blocking on the fd
if (!alive())
- return UNKNOWN;
+ return UNKNOWN_WORD;
switch (*buf) {
case '*':
res = ROOT;
break;
case '-':
- res = COMPOUNDWORD;
+ res = COMPOUND_WORD;
break;
case '\n':
- res = IGNORE;
+ res = IGNORED_WORD;
break;
case '#': // Not found, no near misses and guesses
- res = UNKNOWN;
+ res = UNKNOWN_WORD;
break;
case '?': // Not found, and no near misses, but guesses (guesses are ignored)
case '&': // Not found, but we have near misses
{
- res = MISSED;
+ res = SUGGESTED_WORDS;
char * p = strpbrk(buf, ":");
str = new char[strlen(p) + 1];
e = str;
break;
}
default: // This shouldn't happen, but you know Murphy
- res = UNKNOWN;
+ res = UNKNOWN_WORD;
}
*buf = 0;
- if (res != IGNORE) {
+ if (res != IGNORED_WORD) {
/* wait for ispell to finish */
while (*buf!= '\n')
fgets(buf, 255, in);
}
-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)
{
+ string const encoded = to_iconv_encoding(word.word(), encoding);
+ if (encoded.empty()) {
+ error_ = bformat(
+ _("Could not insert word `%1$s' because it could not be converted to encoding `%2$s'."),
+ word.word(), from_ascii(encoding));
+ return;
+ }
::fputc('*', out); // Insert word in personal dictionary
- ::fputs(word.word().c_str(), out);
+ ::fputs(encoded.c_str(), out);
::fputc('\n', out);
}
void ISpell::accept(WordLangTuple const & word)
{
+ string const encoded = to_iconv_encoding(word.word(), encoding);
+ if (encoded.empty()) {
+ error_ = bformat(
+ _("Could not accept word `%1$s' because it could not be converted to encoding `%2$s'."),
+ word.word(), from_ascii(encoding));
+ return;
+ }
::fputc('@', out); // Accept in this session
- ::fputs(word.word().c_str(), out);
+ ::fputs(encoded.c_str(), out);
::fputc('\n', out);
}
-string const ISpell::error()
+docstring const ISpell::error()
{
- if (error_)
- return error_;
- return "";
+ return error_;
}
+
+
+} // namespace lyx