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
24 #if TIME_WITH_SYS_TIME
25 # include <sys/time.h>
29 # include <sys/time.h>
35 #ifdef HAVE_SYS_SELECT_H
36 # ifdef HAVE_STRINGS_H
37 // <strings.h> is needed at least on AIX because FD_ZERO uses bzero().
38 // BUT we cannot include both string.h and strings.h on Irix 6.5 :(
43 #include <sys/select.h>
47 #include "support/lstrings.h"
52 #include "sp_ispell.h"
57 /// pid for the `ispell' process.
61 /// can be found in src/insets/figinset.C
62 extern void sigchldchecker(pid_t pid, int * status);
65 // ------------------- start special pspell code/class --------------------
69 #include "support/LAssert.h"
71 #define USE_ORIGINAL_MANAGER_FUNCS 1
72 # include <pspell/pspell.h>
74 #include "sp_pspell.h"
78 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
84 PSpell::PSpell(BufferParams const & params, string const & lang)
85 : sc(0), els(0), spell_error_object(0), flag(ISP_UNKNOWN),
88 initialize(params, lang);
97 delete_pspell_string_emulation(els);
101 void PSpell::initialize(BufferParams const &, string const & lang)
103 PspellConfig * config = new_pspell_config();
104 config->replace("language-tag", lang.c_str());
105 spell_error_object = new_pspell_manager(config);
106 if (pspell_error_number(spell_error_object) != 0) {
107 error_ = pspell_error_message(spell_error_object);
110 sc = to_pspell_manager(spell_error_object);
111 spell_error_object = 0;
117 void PSpell::cleanUp()
119 if (spell_error_object) {
120 delete_pspell_can_have_error(spell_error_object);
121 spell_error_object = 0;
126 enum PSpell::spellStatus PSpell::check(string const & word)
131 int word_ok = pspell_manager_check(sc, word.c_str());
132 lyx::Assert(word_ok != -1);
137 PspellWordList const * sugs =
138 pspell_manager_suggest(sc, word.c_str());
139 lyx::Assert(sugs != 0);
140 els = pspell_word_list_elements(sugs);
141 if (pspell_word_list_empty(sugs))
153 pspell_manager_save_all_word_lists(sc);
157 void PSpell::insert(string const & word)
160 pspell_manager_add_to_personal(sc, word.c_str());
164 void PSpell::accept(string const & word)
167 pspell_manager_add_to_session(sc, word.c_str());
171 void PSpell::store(string const & mis, string const & cor)
174 pspell_manager_store_replacement(sc, mis.c_str(), cor.c_str());
178 char const * PSpell::nextMiss()
181 return pspell_string_emulation_next(els);
186 char const * PSpell::error()
192 void PSpell::sigchldhandler(pid_t pid, int * status)
194 sigchldchecker(pid, status);
200 // ------------------- start special ispell code/class --------------------
204 : str(0), flag(ISP_UNKNOWN)
208 ISpell::ISpell(BufferParams const & params, string const & lang)
209 : str(0), flag(ISP_UNKNOWN)
211 initialize(params, lang);
221 char const * ISpell::nextMiss()
223 if (str == 0 || *(e+1) == '\0') return 0;
225 e = strpbrk(b, ",\n");
231 void ISpell::initialize(BufferParams const & params, string const & lang)
233 static char o_buf[BUFSIZ]; // jc: it could be smaller
241 if (pipe(pipein) == -1 || pipe(pipeout) == -1) {
242 lyxerr << "LyX: Can't create pipe for spellchecker!" << endl;
246 if ((out = fdopen(pipein[1], "w")) == 0) {
247 lyxerr << "LyX: Can't create stream for pipe for spellchecker!"
252 if ((in = fdopen(pipeout[0], "r")) == 0) {
253 lyxerr <<"LyX: Can't create stream for pipe for spellchecker!"
258 setvbuf(out, o_buf, _IOLBF, BUFSIZ);
265 lyxerr << "LyX: Can't create child process for spellchecker!"
272 dup2(pipein[0], STDIN_FILENO);
273 dup2(pipeout[1], STDOUT_FILENO);
280 char * tmp = new char[lyxrc.isp_command.length() + 1];
281 lyxrc.isp_command.copy(tmp, lyxrc.isp_command.length());
282 tmp[lyxrc.isp_command.length()] = '\0';
285 string("-a").copy(tmp, 2); tmp[2] = '\0'; // pipe mode
288 if (lang != "default") {
290 string("-d").copy(tmp, 2); tmp[2] = '\0'; // Dictionary file
292 tmp = new char[lang.length() + 1];
293 lang.copy(tmp, lang.length()); tmp[lang.length()] = '\0';
297 if (lyxrc.isp_accept_compound) {
298 // Consider run-together words as legal compounds
300 string("-C").copy(tmp, 2); tmp[2] = '\0';
303 // Report run-together words with
304 // missing blanks as errors
306 string("-B").copy(tmp, 2); tmp[2] = '\0';
309 if (lyxrc.isp_use_esc_chars) {
310 // Specify additional characters that
311 // can be part of a word
313 string("-w").copy(tmp, 2); tmp[2] = '\0';
315 // Put the escape chars in ""s
316 string tms = "\"" + lyxrc.isp_esc_chars + "\"";
317 tmp = new char[tms.length() + 1];
318 tms.copy(tmp, tms.length()); tmp[tms.length()] = '\0';
321 if (lyxrc.isp_use_pers_dict) {
322 // Specify an alternate personal dictionary
324 string("-p").copy(tmp, 2);
327 tmp = new char[lyxrc.isp_pers_dict.length() + 1];
328 lyxrc.isp_pers_dict.copy(tmp, lyxrc.isp_pers_dict.length());
329 tmp[lyxrc.isp_pers_dict.length()] = '\0';
332 if (lyxrc.isp_use_input_encoding &&
333 params.inputenc != "default") {
334 string enc = (params.inputenc == "auto")
335 ? params.language->encoding()->LatexName()
337 string::size_type n = enc.length();
339 string("-T").copy(tmp, 2);
341 argv[argc++] = tmp; // Input encoding
342 tmp = new char[n + 1];
350 execvp(argv[0], const_cast<char * const *>(argv));
352 // free the memory used by string::copy in the
354 for (int i = 0; i < argc - 1; ++i)
357 lyxerr << "LyX: Failed to start ispell!" << endl;
361 /* Parent process: Read ispells identification message */
362 // Hmm...what are we using this id msg for? Nothing? (Lgb)
363 // Actually I used it to tell if it's truly Ispell or if it's
364 // aspell -- (kevinatk@home.com)
370 FD_SET(pipeout[0], &infds);
371 tv.tv_sec = 15; // fifteen second timeout. Probably too much,
372 // but it can't really hurt.
375 // Configure provides us with macros which are supposed to do
376 // the right typecast.
377 retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1),
378 SELECT_TYPE_ARG234 (&infds),
381 SELECT_TYPE_ARG5 (&tv));
384 // Ok, do the reading. We don't have to FD_ISSET since
385 // there is only one fd in infds.
386 fgets(buf, 2048, in);
388 // determine if the spell checker is really Aspell
389 if (strstr(buf, "Aspell"))
390 actual_spell_checker = ASC_ASPELL;
392 actual_spell_checker = ASC_ISPELL;
394 fputs("!\n", out); // Set terse mode (silently accept correct words)
397 } else if (retval == 0) {
398 // timeout. Give nice message to user.
399 lyxerr << "Ispell read timed out, what now?" << endl;
400 // This probably works but could need some thought
408 // Select returned error
409 lyxerr << "Select on ispell returned error, what now?" << endl;
416 "The spellcheck-process has died for some reason.\n"
417 "*One* possible reason could be that you do not have\n"
418 "a dictionary file for the language of this document\n"
420 "Check your spellchecker or set another dictionary\n"
421 "in the Spellchecker Options menu.\n\n";
430 return isp_pid != -1;
434 void ISpell::cleanUp()
440 enum ISpell::spellStatus ISpell::check(string const & word)
442 //Please rewrite to use string.
444 ::fputs(word.c_str(), out);
448 ::fgets(buf, 1024, in);
450 // I think we have to check if ispell is still alive here because
451 // the signal-handler could have disabled blocking on the fd
452 if (!alive()) return ISP_UNKNOWN;
455 case '*': // Word found
458 case '+': // Word found through affix removal
461 case '-': // Word found through compound formation
462 flag = ISP_COMPOUNDWORD;
464 case '\n': // Number or when in terse mode: no problems
467 case '#': // Not found, no near misses and guesses
470 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
471 case '&': // Not found, but we have near misses
474 char * p = strpbrk(buf, ":");
475 str = new char[strlen(p) + 1];
480 default: // This shouldn't happend, but you know Murphy
485 if (flag!= ISP_IGNORE) {
486 while (*buf!= '\n') fgets(buf, 255, in); /* wait for ispell to finish */
494 // Note: If you decide to optimize this out when it is not
495 // needed please note that when Aspell is used this command
496 // is also needed to save the replacement dictionary.
497 // -- Kevin Atkinson (kevinatk@home.com)
499 fputs("#\n", out); // Save personal dictionary
506 void ISpell::insert(string const & word)
508 ::fputc('*', out); // Insert word in personal dictionary
509 ::fputs(word.c_str(), out);
514 void ISpell::accept(string const & word)
516 ::fputc('@', out); // Accept in this session
517 ::fputs(word.c_str(), out);
522 void ISpell::store(string const & mis, string const & cor)
524 if (actual_spell_checker == ASC_ASPELL) {
525 ::fputs("$$ra ", out);
526 ::fputs(mis.c_str(), out);
528 ::fputs(cor.c_str(), out);
534 void ISpell::sigchldhandler(pid_t pid, int * status)
537 if (pid == isp_pid) {
539 // set the file descriptor to nonblocking so we can
541 fcntl(isp_fd, F_SETFL, O_NONBLOCK);
544 sigchldchecker(pid, status);
548 char const * ISpell::error()