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"
56 #ifndef CXX_GLOBAL_CSTD
66 /// pid for the `ispell' process.
71 // ------------------- start special pspell code/class --------------------
75 #include "support/LAssert.h"
77 #define USE_ORIGINAL_MANAGER_FUNCS 1
78 # include <pspell/pspell.h>
80 #include "sp_pspell.h"
84 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
90 PSpell::PSpell(BufferParams const & params, string const & lang)
91 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
94 initialize(params, lang);
103 delete_pspell_string_emulation(els);
107 void PSpell::initialize(BufferParams const &, string const & lang)
109 PspellConfig * config = new_pspell_config();
110 config->replace("language-tag", lang.c_str());
111 spell_error_object = new_pspell_manager(config);
112 if (pspell_error_number(spell_error_object) != 0) {
113 error_ = pspell_error_message(spell_error_object);
116 sc = to_pspell_manager(spell_error_object);
117 spell_error_object = 0;
123 void PSpell::cleanUp()
125 if (spell_error_object) {
126 delete_pspell_can_have_error(spell_error_object);
127 spell_error_object = 0;
132 enum PSpell::spellStatus PSpell::check(string const & word)
137 int word_ok = pspell_manager_check(sc, word.c_str());
138 lyx::Assert(word_ok != -1);
143 PspellWordList const * sugs =
144 pspell_manager_suggest(sc, word.c_str());
145 lyx::Assert(sugs != 0);
146 els = pspell_word_list_elements(sugs);
147 if (pspell_word_list_empty(sugs))
159 pspell_manager_save_all_word_lists(sc);
163 void PSpell::insert(string const & word)
166 pspell_manager_add_to_personal(sc, word.c_str());
170 void PSpell::accept(string const & word)
173 pspell_manager_add_to_session(sc, word.c_str());
177 void PSpell::store(string const & mis, string const & cor)
180 pspell_manager_store_replacement(sc, mis.c_str(), cor.c_str());
184 char const * PSpell::nextMiss()
187 return pspell_string_emulation_next(els);
192 char const * PSpell::error()
201 // ------------------- start special ispell code/class --------------------
205 : str(0), flag(ISP_UNKNOWN)
209 ISpell::ISpell(BufferParams const & params, string const & lang)
210 : str(0), flag(ISP_UNKNOWN)
212 initialize(params, lang);
222 char const * ISpell::nextMiss()
224 if (str == 0 || *(e+1) == '\0') return 0;
226 e = strpbrk(b, ",\n");
232 void ISpell::initialize(BufferParams const & params, string const & lang)
234 static char o_buf[BUFSIZ]; // jc: it could be smaller
242 if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
243 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
247 if ((out = fdopen(pipein[1], "w")) == 0) {
248 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
253 if ((in = fdopen(pipeout[0], "r")) == 0) {
254 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
259 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
266 lyxerr << "LyX: Can't create child process for spellchecker!"
273 dup2(pipein[0], STDIN_FILENO);
274 dup2(pipeout[1], STDOUT_FILENO);
281 char * tmp = new char[lyxrc.isp_command.length() + 1];
282 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
283 tmp[lyxrc.isp_command.length()] = '\0';
286 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
289 if (lang != "default") {
291 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
293 tmp = new char[lang.length() + 1];
294 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
298 if (lyxrc.isp_accept_compound) {
299 // Consider run-together words as legal compounds
301 string("-C").copy(tmp, 2); tmp[2] = '\0';
304 // Report run-together words with
305 // missing blanks as errors
307 string("-B").copy(tmp, 2); tmp[2] = '\0';
310 if (lyxrc.isp_use_esc_chars) {
311 // Specify additional characters that
312 // can be part of a word
314 string("-w").copy(tmp, 2); tmp[2] = '\0';
316 // Put the escape chars in ""s
317 string tms = "\"" + lyxrc.isp_esc_chars + "\"";
318 tmp = new char[tms.length() + 1];
319 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
322 if (lyxrc.isp_use_pers_dict) {
323 // Specify an alternate personal dictionary
325 string("-p").copy(tmp, 2);
328 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
329 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
330 tmp[lyxrc.isp_pers_dict.length()] = '\0';
333 if (lyxrc.isp_use_input_encoding &&
334 params.inputenc != "default") {
335 string enc = (params.inputenc == "auto")
336 ? params.language->encoding()->LatexName()
338 string::size_type n = enc.length();
340 string("-T").copy(tmp, 2);
342 argv[argc++] = tmp; // Input encoding
343 tmp = new char[n + 1];
351 execvp(argv[0], const_cast<char * const *>(argv));
353 // free the memory used by string::copy in the
355 for (int i = 0; i < argc - 1; ++i)
358 lyxerr << "LyX: Failed to start ispell!" << endl;
362 /* Parent process: Read ispells identification message */
363 // Hmm...what are we using this id msg for? Nothing? (Lgb)
364 // Actually I used it to tell if it's truly Ispell or if it's
365 // aspell -- (kevinatk@home.com)
371 FD_SET(pipeout[0], &infds);
372 tv.tv_sec = 15; // fifteen second timeout. Probably too much,
373 // but it can't really hurt.
376 // Configure provides us with macros which are supposed to do
377 // the right typecast.
378 retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
379 SELECT_TYPE_ARG234 (&infds),
382 SELECT_TYPE_ARG5 (&tv));
385 // Ok, do the reading. We don't have to FD_ISSET since
386 // there is only one fd in infds.
387 fgets(buf, 2048, in);
389 // determine if the spell checker is really Aspell
390 if (strstr(buf, "Aspell"))
391 actual_spell_checker = ASC_ASPELL;
393 actual_spell_checker = ASC_ISPELL;
395 fputs("!\n", out); // Set terse mode (silently accept correct words)
398 } else if (retval == 0) {
399 // timeout. Give nice message to user.
400 lyxerr << "Ispell read timed out, what now?" << endl;
401 // This probably works but could need some thought
409 // Select returned error
410 lyxerr << "Select on ispell returned error, what now?" << endl;
417 "The spellcheck-process has died for some reason.\n"
418 "*One* possible reason could be that you do not have\n"
419 "a dictionary file for the language of this document\n"
421 "Check your spellchecker or set another dictionary\n"
422 "in the Spellchecker Options menu.\n\n";
429 /* FIXME: this is a minimalist solution until the above
430 * code is able to work with forkedcall.h. We only need
431 * to reap the zombies here.
433 void reapSpellchecker(void)
438 waitpid(isp_pid, 0, WNOHANG);
444 return isp_pid != -1;
448 void ISpell::cleanUp()
454 enum ISpell::spellStatus ISpell::check(string const & word)
456 //Please rewrite to use string.
458 ::fputs(word.c_str(), out);
462 ::fgets(buf, 1024, in);
464 // I think we have to check if ispell is still alive here because
465 // the signal-handler could have disabled blocking on the fd
466 if (!alive()) return ISP_UNKNOWN;
469 case '*': // Word found
472 case '+': // Word found through affix removal
475 case '-': // Word found through compound formation
476 flag = ISP_COMPOUNDWORD;
478 case '\n': // Number or when in terse mode: no problems
481 case '#': // Not found, no near misses and guesses
484 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
485 case '&': // Not found, but we have near misses
488 char * p = strpbrk(buf, ":");
489 str = new char[strlen(p) + 1];
494 default: // This shouldn't happend, but you know Murphy
499 if (flag!= ISP_IGNORE) {
500 while (*buf!= '\n') fgets(buf, 255, in); /* wait for ispell to finish */
508 // Note: If you decide to optimize this out when it is not
509 // needed please note that when Aspell is used this command
510 // is also needed to save the replacement dictionary.
511 // -- Kevin Atkinson (kevinatk@home.com)
513 fputs("#\n", out); // Save personal dictionary
520 void ISpell::insert(string const & word)
522 ::fputc('*', out); // Insert word in personal dictionary
523 ::fputs(word.c_str(), out);
528 void ISpell::accept(string const & word)
530 ::fputc('@', out); // Accept in this session
531 ::fputs(word.c_str(), out);
536 void ISpell::store(string const & mis, string const & cor)
538 if (actual_spell_checker == ASC_ASPELL) {
539 ::fputs("$$ra ", out);
540 ::fputs(mis.c_str(), out);
542 ::fputs(cor.c_str(), out);
548 char const * ISpell::error()