1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 2001 The LyX Team.
8 * ======================================================
11 * \author Kevin Atkinson
17 #pragma implementation
20 #include <sys/types.h>
26 #if TIME_WITH_SYS_TIME
27 # include <sys/time.h>
31 # include <sys/time.h>
37 #ifdef HAVE_SYS_SELECT_H
38 # ifdef HAVE_STRINGS_H
39 // <strings.h> is needed at least on AIX because FD_ZERO uses bzero().
40 // BUT we cannot include both string.h and strings.h on Irix 6.5 :(
45 #include <sys/select.h>
49 #include "support/lstrings.h"
54 #include "sp_ispell.h"
59 /// pid for the `ispell' process.
64 // ------------------- start special pspell code/class --------------------
68 #include "support/LAssert.h"
70 #define USE_ORIGINAL_MANAGER_FUNCS 1
71 # include <pspell/pspell.h>
73 #include "sp_pspell.h"
77 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
83 PSpell::PSpell(BufferParams const & params, string const & lang)
84 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
87 initialize(params, lang);
96 delete_pspell_string_emulation(els);
100 void PSpell::initialize(BufferParams const &, string const & lang)
102 PspellConfig * config = new_pspell_config();
103 config->replace("language-tag", lang.c_str());
104 spell_error_object = new_pspell_manager(config);
105 if (pspell_error_number(spell_error_object) != 0) {
106 error_ = pspell_error_message(spell_error_object);
109 sc = to_pspell_manager(spell_error_object);
110 spell_error_object = 0;
116 void PSpell::cleanUp()
118 if (spell_error_object) {
119 delete_pspell_can_have_error(spell_error_object);
120 spell_error_object = 0;
125 enum PSpell::spellStatus PSpell::check(string const & word)
130 int word_ok = pspell_manager_check(sc, word.c_str());
131 lyx::Assert(word_ok != -1);
136 PspellWordList const * sugs =
137 pspell_manager_suggest(sc, word.c_str());
138 lyx::Assert(sugs != 0);
139 els = pspell_word_list_elements(sugs);
140 if (pspell_word_list_empty(sugs))
152 pspell_manager_save_all_word_lists(sc);
156 void PSpell::insert(string const & word)
159 pspell_manager_add_to_personal(sc, word.c_str());
163 void PSpell::accept(string const & word)
166 pspell_manager_add_to_session(sc, word.c_str());
170 void PSpell::store(string const & mis, string const & cor)
173 pspell_manager_store_replacement(sc, mis.c_str(), cor.c_str());
177 char const * PSpell::nextMiss()
180 return pspell_string_emulation_next(els);
185 char const * PSpell::error()
194 // ------------------- start special ispell code/class --------------------
198 : str(0), flag(ISP_UNKNOWN)
202 ISpell::ISpell(BufferParams const & params, string const & lang)
203 : str(0), flag(ISP_UNKNOWN)
205 initialize(params, lang);
215 char const * ISpell::nextMiss()
217 if (str == 0 || *(e+1) == '\0') return 0;
219 e = strpbrk(b, ",\n");
225 void ISpell::initialize(BufferParams const & params, string const & lang)
227 static char o_buf[BUFSIZ]; // jc: it could be smaller
235 if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
236 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
240 if ((out = fdopen(pipein[1], "w")) == 0) {
241 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
246 if ((in = fdopen(pipeout[0], "r")) == 0) {
247 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
252 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
259 lyxerr << "LyX: Can't create child process for spellchecker!"
266 dup2(pipein[0], STDIN_FILENO);
267 dup2(pipeout[1], STDOUT_FILENO);
274 char * tmp = new char[lyxrc.isp_command.length() + 1];
275 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
276 tmp[lyxrc.isp_command.length()] = '\0';
279 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
282 if (lang != "default") {
284 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
286 tmp = new char[lang.length() + 1];
287 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
291 if (lyxrc.isp_accept_compound) {
292 // Consider run-together words as legal compounds
294 string("-C").copy(tmp, 2); tmp[2] = '\0';
297 // Report run-together words with
298 // missing blanks as errors
300 string("-B").copy(tmp, 2); tmp[2] = '\0';
303 if (lyxrc.isp_use_esc_chars) {
304 // Specify additional characters that
305 // can be part of a word
307 string("-w").copy(tmp, 2); tmp[2] = '\0';
309 // Put the escape chars in ""s
310 string tms = "\"" + lyxrc.isp_esc_chars + "\"";
311 tmp = new char[tms.length() + 1];
312 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
315 if (lyxrc.isp_use_pers_dict) {
316 // Specify an alternate personal dictionary
318 string("-p").copy(tmp, 2);
321 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
322 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
323 tmp[lyxrc.isp_pers_dict.length()] = '\0';
326 if (lyxrc.isp_use_input_encoding &&
327 params.inputenc != "default") {
328 string enc = (params.inputenc == "auto")
329 ? params.language->encoding()->LatexName()
331 string::size_type n = enc.length();
333 string("-T").copy(tmp, 2);
335 argv[argc++] = tmp; // Input encoding
336 tmp = new char[n + 1];
344 execvp(argv[0], const_cast<char * const *>(argv));
346 // free the memory used by string::copy in the
348 for (int i = 0; i < argc - 1; ++i)
351 lyxerr << "LyX: Failed to start ispell!" << endl;
355 /* Parent process: Read ispells identification message */
356 // Hmm...what are we using this id msg for? Nothing? (Lgb)
357 // Actually I used it to tell if it's truly Ispell or if it's
358 // aspell -- (kevinatk@home.com)
364 FD_SET(pipeout[0], &infds);
365 tv.tv_sec = 15; // fifteen second timeout. Probably too much,
366 // but it can't really hurt.
369 // Configure provides us with macros which are supposed to do
370 // the right typecast.
371 retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
372 SELECT_TYPE_ARG234 (&infds),
375 SELECT_TYPE_ARG5 (&tv));
378 // Ok, do the reading. We don't have to FD_ISSET since
379 // there is only one fd in infds.
380 fgets(buf, 2048, in);
382 // determine if the spell checker is really Aspell
383 if (strstr(buf, "Aspell"))
384 actual_spell_checker = ASC_ASPELL;
386 actual_spell_checker = ASC_ISPELL;
388 fputs("!\n", out); // Set terse mode (silently accept correct words)
391 } else if (retval == 0) {
392 // timeout. Give nice message to user.
393 lyxerr << "Ispell read timed out, what now?" << endl;
394 // This probably works but could need some thought
402 // Select returned error
403 lyxerr << "Select on ispell returned error, what now?" << endl;
410 "The spellcheck-process has died for some reason.\n"
411 "*One* possible reason could be that you do not have\n"
412 "a dictionary file for the language of this document\n"
414 "Check your spellchecker or set another dictionary\n"
415 "in the Spellchecker Options menu.\n\n";
422 /* FIXME: this is a minimalist solution until the above
423 * code is able to work with forkedcall.h. We only need
424 * to reap the zombies here.
426 void reapSpellchecker(void)
431 waitpid(isp_pid, 0, WNOHANG);
437 return isp_pid != -1;
441 void ISpell::cleanUp()
447 enum ISpell::spellStatus ISpell::check(string const & word)
449 //Please rewrite to use string.
451 ::fputs(word.c_str(), out);
455 ::fgets(buf, 1024, in);
457 // I think we have to check if ispell is still alive here because
458 // the signal-handler could have disabled blocking on the fd
459 if (!alive()) return ISP_UNKNOWN;
462 case '*': // Word found
465 case '+': // Word found through affix removal
468 case '-': // Word found through compound formation
469 flag = ISP_COMPOUNDWORD;
471 case '\n': // Number or when in terse mode: no problems
474 case '#': // Not found, no near misses and guesses
477 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
478 case '&': // Not found, but we have near misses
481 char * p = strpbrk(buf, ":");
482 str = new char[strlen(p) + 1];
487 default: // This shouldn't happend, but you know Murphy
492 if (flag!= ISP_IGNORE) {
493 while (*buf!= '\n') fgets(buf, 255, in); /* wait for ispell to finish */
501 // Note: If you decide to optimize this out when it is not
502 // needed please note that when Aspell is used this command
503 // is also needed to save the replacement dictionary.
504 // -- Kevin Atkinson (kevinatk@home.com)
506 fputs("#\n", out); // Save personal dictionary
513 void ISpell::insert(string const & word)
515 ::fputc('*', out); // Insert word in personal dictionary
516 ::fputs(word.c_str(), out);
521 void ISpell::accept(string const & word)
523 ::fputc('@', out); // Accept in this session
524 ::fputs(word.c_str(), out);
529 void ISpell::store(string const & mis, string const & cor)
531 if (actual_spell_checker == ASC_ASPELL) {
532 ::fputs("$$ra ", out);
533 ::fputs(mis.c_str(), out);
535 ::fputs(cor.c_str(), out);
541 char const * ISpell::error()