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
67 /// pid for the `ispell' process.
72 // ------------------- start special pspell code/class --------------------
76 #include "support/LAssert.h"
78 #define USE_ORIGINAL_MANAGER_FUNCS 1
79 # include <pspell/pspell.h>
81 #include "sp_pspell.h"
85 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
91 PSpell::PSpell(BufferParams const & params, string const & lang)
92 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
95 initialize(params, lang);
104 delete_pspell_string_emulation(els);
108 void PSpell::initialize(BufferParams const &, string const & lang)
110 PspellConfig * config = new_pspell_config();
111 config->replace("language-tag", lang.c_str());
112 spell_error_object = new_pspell_manager(config);
113 if (pspell_error_number(spell_error_object) != 0) {
114 error_ = pspell_error_message(spell_error_object);
117 sc = to_pspell_manager(spell_error_object);
118 spell_error_object = 0;
124 void PSpell::cleanUp()
126 if (spell_error_object) {
127 delete_pspell_can_have_error(spell_error_object);
128 spell_error_object = 0;
133 enum PSpell::spellStatus PSpell::check(string const & word)
138 int word_ok = pspell_manager_check(sc, word.c_str());
139 lyx::Assert(word_ok != -1);
144 PspellWordList const * sugs =
145 pspell_manager_suggest(sc, word.c_str());
146 lyx::Assert(sugs != 0);
147 els = pspell_word_list_elements(sugs);
148 if (pspell_word_list_empty(sugs))
160 pspell_manager_save_all_word_lists(sc);
164 void PSpell::insert(string const & word)
167 pspell_manager_add_to_personal(sc, word.c_str());
171 void PSpell::accept(string const & word)
174 pspell_manager_add_to_session(sc, word.c_str());
178 void PSpell::store(string const & mis, string const & cor)
181 pspell_manager_store_replacement(sc, mis.c_str(), cor.c_str());
185 char const * PSpell::nextMiss()
188 return pspell_string_emulation_next(els);
193 char const * PSpell::error()
202 // ------------------- start special ispell code/class --------------------
206 : str(0), flag(ISP_UNKNOWN)
210 ISpell::ISpell(BufferParams const & params, string const & lang)
211 : str(0), flag(ISP_UNKNOWN)
213 initialize(params, lang);
223 char const * ISpell::nextMiss()
225 if (str == 0 || *(e+1) == '\0') return 0;
227 e = strpbrk(b, ",\n");
233 void ISpell::initialize(BufferParams const & params, string const & lang)
235 static char o_buf[BUFSIZ]; // jc: it could be smaller
243 if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
244 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
248 if ((out = fdopen(pipein[1], "w")) == 0) {
249 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
254 if ((in = fdopen(pipeout[0], "r")) == 0) {
255 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
260 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
267 lyxerr << "LyX: Can't create child process for spellchecker!"
274 dup2(pipein[0], STDIN_FILENO);
275 dup2(pipeout[1], STDOUT_FILENO);
282 char * tmp = new char[lyxrc.isp_command.length() + 1];
283 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
284 tmp[lyxrc.isp_command.length()] = '\0';
287 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
290 if (lang != "default") {
292 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
294 tmp = new char[lang.length() + 1];
295 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
299 if (lyxrc.isp_accept_compound) {
300 // Consider run-together words as legal compounds
302 string("-C").copy(tmp, 2); tmp[2] = '\0';
305 // Report run-together words with
306 // missing blanks as errors
308 string("-B").copy(tmp, 2); tmp[2] = '\0';
311 if (lyxrc.isp_use_esc_chars) {
312 // Specify additional characters that
313 // can be part of a word
315 string("-w").copy(tmp, 2); tmp[2] = '\0';
317 // Put the escape chars in ""s
318 string tms = "\"" + lyxrc.isp_esc_chars + "\"";
319 tmp = new char[tms.length() + 1];
320 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
323 if (lyxrc.isp_use_pers_dict) {
324 // Specify an alternate personal dictionary
326 string("-p").copy(tmp, 2);
329 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
330 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
331 tmp[lyxrc.isp_pers_dict.length()] = '\0';
334 if (lyxrc.isp_use_input_encoding &&
335 params.inputenc != "default") {
336 string enc = (params.inputenc == "auto")
337 ? params.language->encoding()->LatexName()
339 string::size_type n = enc.length();
341 string("-T").copy(tmp, 2);
343 argv[argc++] = tmp; // Input encoding
344 tmp = new char[n + 1];
352 execvp(argv[0], const_cast<char * const *>(argv));
354 // free the memory used by string::copy in the
356 for (int i = 0; i < argc - 1; ++i)
359 lyxerr << "LyX: Failed to start ispell!" << endl;
363 /* Parent process: Read ispells identification message */
364 // Hmm...what are we using this id msg for? Nothing? (Lgb)
365 // Actually I used it to tell if it's truly Ispell or if it's
366 // aspell -- (kevinatk@home.com)
372 FD_SET(pipeout[0], &infds);
373 tv.tv_sec = 15; // fifteen second timeout. Probably too much,
374 // but it can't really hurt.
377 // Configure provides us with macros which are supposed to do
378 // the right typecast.
379 retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
380 SELECT_TYPE_ARG234 (&infds),
383 SELECT_TYPE_ARG5 (&tv));
386 // Ok, do the reading. We don't have to FD_ISSET since
387 // there is only one fd in infds.
388 fgets(buf, 2048, in);
390 // determine if the spell checker is really Aspell
391 if (strstr(buf, "Aspell"))
392 actual_spell_checker = ASC_ASPELL;
394 actual_spell_checker = ASC_ISPELL;
396 fputs("!\n", out); // Set terse mode (silently accept correct words)
399 } else if (retval == 0) {
400 // timeout. Give nice message to user.
401 lyxerr << "Ispell read timed out, what now?" << endl;
402 // This probably works but could need some thought
410 // Select returned error
411 lyxerr << "Select on ispell returned error, what now?" << endl;
418 "The spellcheck-process has died for some reason.\n"
419 "*One* possible reason could be that you do not have\n"
420 "a dictionary file for the language of this document\n"
422 "Check your spellchecker or set another dictionary\n"
423 "in the Spellchecker Options menu.\n\n";
430 /* FIXME: this is a minimalist solution until the above
431 * code is able to work with forkedcall.h. We only need
432 * to reap the zombies here.
434 void reapSpellchecker(void)
439 waitpid(isp_pid, 0, WNOHANG);
445 return isp_pid != -1;
449 void ISpell::cleanUp()
455 enum ISpell::spellStatus ISpell::check(string const & word)
457 //Please rewrite to use string.
459 ::fputs(word.c_str(), out);
463 ::fgets(buf, 1024, in);
465 // I think we have to check if ispell is still alive here because
466 // the signal-handler could have disabled blocking on the fd
467 if (!alive()) return ISP_UNKNOWN;
470 case '*': // Word found
473 case '+': // Word found through affix removal
476 case '-': // Word found through compound formation
477 flag = ISP_COMPOUNDWORD;
479 case '\n': // Number or when in terse mode: no problems
482 case '#': // Not found, no near misses and guesses
485 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
486 case '&': // Not found, but we have near misses
489 char * p = strpbrk(buf, ":");
490 str = new char[strlen(p) + 1];
495 default: // This shouldn't happend, but you know Murphy
500 if (flag!= ISP_IGNORE) {
501 while (*buf!= '\n') fgets(buf, 255, in); /* wait for ispell to finish */
509 // Note: If you decide to optimize this out when it is not
510 // needed please note that when Aspell is used this command
511 // is also needed to save the replacement dictionary.
512 // -- Kevin Atkinson (kevinatk@home.com)
514 fputs("#\n", out); // Save personal dictionary
521 void ISpell::insert(string const & word)
523 ::fputc('*', out); // Insert word in personal dictionary
524 ::fputs(word.c_str(), out);
529 void ISpell::accept(string const & word)
531 ::fputc('@', out); // Accept in this session
532 ::fputs(word.c_str(), out);
537 void ISpell::store(string const & mis, string const & cor)
539 if (actual_spell_checker == ASC_ASPELL) {
540 ::fputs("$$ra ", out);
541 ::fputs(mis.c_str(), out);
543 ::fputs(cor.c_str(), out);
549 char const * ISpell::error()