3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Angus Leeming
10 * Full author contact details are available in file CREDITS.
17 #include "BufferParams.h"
21 #include "WordLangTuple.h"
23 #include "support/debug.h"
24 #include "support/gettext.h"
25 #include "support/ForkedCalls.h"
26 #include "support/lstrings.h"
27 #include "support/unicode.h"
29 // HP-UX 11.x doesn't have this header
30 #ifdef HAVE_SYS_SELECT_H
31 # include <sys/select.h>
33 #ifdef HAVE_SYS_TIME_H
34 # include <sys/time.h>
37 using boost::shared_ptr
;
40 using namespace lyx::support
;
46 class LaunchIspell
: public ForkedProcess
50 LaunchIspell(BufferParams
const & p
, string
const & l
,
51 int * in
, int * out
, int * err
)
52 : params(p
), lang(l
), pipein(in
), pipeout(out
), pipeerr(err
) {}
54 virtual shared_ptr
<ForkedProcess
> clone() const {
55 return shared_ptr
<ForkedProcess
>(new LaunchIspell(*this));
61 virtual int generateChild();
64 BufferParams
const & params
;
72 int LaunchIspell::start()
74 command_
= lyxrc
.isp_command
;
79 int LaunchIspell::generateChild()
81 pid_t isp_pid
= fork();
84 // failed (-1) or parent process (>0)
89 dup2(pipein
[0], STDIN_FILENO
);
90 dup2(pipeout
[1], STDOUT_FILENO
);
91 dup2(pipeerr
[1], STDERR_FILENO
);
102 char * tmp
= new char[lyxrc
.isp_command
.length() + 1];
103 lyxrc
.isp_command
.copy(tmp
, lyxrc
.isp_command
.length());
104 tmp
[lyxrc
.isp_command
.length()] = '\0';
107 string("-a").copy(tmp
, 2); tmp
[2] = '\0'; // pipe mode
110 if (lang
!= "default") {
112 string("-d").copy(tmp
, 2); tmp
[2] = '\0'; // Dictionary file
114 tmp
= new char[lang
.length() + 1];
115 lang
.copy(tmp
, lang
.length()); tmp
[lang
.length()] = '\0';
119 if (lyxrc
.isp_accept_compound
) {
120 // Consider run-together words as legal compounds
122 string("-C").copy(tmp
, 2); tmp
[2] = '\0';
125 // Report run-together words with
126 // missing blanks as errors
128 string("-B").copy(tmp
, 2); tmp
[2] = '\0';
131 if (lyxrc
.isp_use_esc_chars
) {
132 // Specify additional characters that
133 // can be part of a word
135 string("-w").copy(tmp
, 2); tmp
[2] = '\0';
137 // Put the escape chars in ""s
138 string tms
= '"' + lyxrc
.isp_esc_chars
+ '"';
139 tmp
= new char[tms
.length() + 1];
140 tms
.copy(tmp
, tms
.length()); tmp
[tms
.length()] = '\0';
143 if (lyxrc
.isp_use_pers_dict
) {
144 // Specify an alternate personal dictionary
146 string("-p").copy(tmp
, 2);
149 tmp
= new char[lyxrc
.isp_pers_dict
.length() + 1];
150 lyxrc
.isp_pers_dict
.copy(tmp
, lyxrc
.isp_pers_dict
.length());
151 tmp
[lyxrc
.isp_pers_dict
.length()] = '\0';
154 if (lyxrc
.isp_use_input_encoding
&&
155 params
.inputenc
!= "default") {
156 string enc
= (params
.inputenc
== "auto")
157 ? params
.language
->encoding()->latexName()
159 size_t const n
= enc
.length();
161 string("-T").copy(tmp
, 2);
163 argv
[argc
++] = tmp
; // Input encoding
164 tmp
= new char[n
+ 1];
172 execvp(argv
[0], const_cast<char * const *>(argv
));
174 // free the memory used by string::copy in the
176 for (int i
= 0; i
< argc
- 1; ++i
)
179 lyxerr
<< "LyX: Failed to start ispell!" << endl
;
184 string
const to_iconv_encoding(docstring
const & s
, string
const & encoding
)
186 if (lyxrc
.isp_use_input_encoding
) {
187 vector
<char> const encoded
=
188 ucs4_to_eightbit(s
.data(), s
.length(), encoding
);
189 return string(encoded
.begin(), encoded
.end());
191 // FIXME UNICODE: we don't need to convert to UTF8, but probably to the locale encoding
196 docstring
const from_iconv_encoding(string
const & s
, string
const & encoding
)
198 if (lyxrc
.isp_use_input_encoding
) {
199 vector
<char_type
> const ucs4
=
200 eightbit_to_ucs4(s
.data(), s
.length(), encoding
);
201 return docstring(ucs4
.begin(), ucs4
.end());
203 // FIXME UNICODE: s is not in UTF8, but probably the locale encoding
210 ISpell::ISpell(BufferParams
const & params
, string
const & lang
)
211 : in(0), out(0), inerr(0), str(0)
213 //LYXERR(Debug::GUI, "Created ispell");
215 encoding
= params
.encoding().iconvName();
217 // static due to the setvbuf. Ugly.
218 static char o_buf
[BUFSIZ
];
220 // We need to throw an exception not do this
221 pipein
[0] = pipein
[1] = pipeout
[0] = pipeout
[1]
222 = pipeerr
[0] = pipeerr
[1] = -1;
224 // This is what happens when goto gets banned.
226 if (pipe(pipein
) == -1) {
227 error_
= _("Can't create pipe for spellchecker.");
231 if (pipe(pipeout
) == -1) {
234 error_
= _("Can't create pipe for spellchecker.");
238 if (pipe(pipeerr
) == -1) {
243 error_
= _("Can't create pipe for spellchecker.");
247 if ((out
= fdopen(pipein
[1], "w")) == 0) {
248 error_
= _("Can't open pipe for spellchecker.");
252 if ((in
= fdopen(pipeout
[0], "r")) == 0) {
253 error_
= _("Can't open pipe for spellchecker.");
257 if ((inerr
= fdopen(pipeerr
[0], "r")) == 0) {
258 error_
= _("Can't open pipe for spellchecker.");
262 setvbuf(out
, o_buf
, _IOLBF
, BUFSIZ
);
264 LaunchIspell
* li
= new LaunchIspell(params
, lang
, pipein
, pipeout
, pipeerr
);
266 if (li
->start() == -1) {
267 error_
= _("Could not create an ispell process.\nYou may not have "
268 "the right languages installed.");
273 // Parent process: Read ispells identification message
276 bool error
= select(err_read
);
280 // Set terse mode (silently accept correct words)
285 // must have read something from stderr
286 // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
287 error_
= from_utf8(buf
);
289 // select returned error
290 error_
= _("The ispell process returned an error.\nPerhaps "
291 "it has been configured wrongly ?");
307 //LYXERR(Debug::GUI, "Killing ispell");
316 fputs("#\n", out
); // Save personal dictionary
332 bool ISpell::select(bool & err_read
)
338 FD_SET(pipeout
[0], &infds
);
339 FD_SET(pipeerr
[0], &infds
);
343 retval
= ::select(SELECT_TYPE_ARG1 (max(pipeout
[0], pipeerr
[0]) + 1),
344 SELECT_TYPE_ARG234 (&infds
),
347 SELECT_TYPE_ARG5 (&tv
));
353 if (FD_ISSET(pipeerr
[0], &infds
)) {
354 fgets(buf
, BUFSIZ
, inerr
);
359 fgets(buf
, BUFSIZ
, in
);
365 docstring
const ISpell::nextMiss()
367 // Well, somebody is a sick fuck.
369 if (str
== 0 || *(e
+1) == '\0')
372 e
= strpbrk(b
, ",\n");
375 return from_iconv_encoding(b
, encoding
);
382 return child_
.get() && child_
->running();
386 enum ISpell::Result
ISpell::check(WordLangTuple
const & word
)
388 // FIXME Please rewrite to use string.
392 string
const encoded
= to_iconv_encoding(word
.word(), encoding
);
393 if (encoded
.empty()) {
395 _("Could not check word `%1$s' because it could not be converted to encoding `%2$s'."),
396 word
.word(), from_ascii(encoding
));
399 ::fputs(encoded
.c_str(), out
);
403 bool error
= select(err_read
);
406 error_
= _("Could not communicate with the ispell spellchecker process.");
411 // FIXME UNICODE: buf is not in UTF8, but probably the locale encoding
412 error_
= from_utf8(buf
);
416 // I think we have to check if ispell is still alive here because
417 // the signal-handler could have disabled blocking on the fd
434 case '#': // Not found, no near misses and guesses
437 case '?': // Not found, and no near misses, but guesses (guesses are ignored)
438 case '&': // Not found, but we have near misses
440 res
= SUGGESTED_WORDS
;
441 char * p
= strpbrk(buf
, ":");
442 str
= new char[strlen(p
) + 1];
447 default: // This shouldn't happen, but you know Murphy
452 if (res
!= IGNORED_WORD
) {
453 /* wait for ispell to finish */
461 void ISpell::insert(WordLangTuple
const & word
)
463 string
const encoded
= to_iconv_encoding(word
.word(), encoding
);
464 if (encoded
.empty()) {
466 _("Could not insert word `%1$s' because it could not be converted to encoding `%2$s'."),
467 word
.word(), from_ascii(encoding
));
470 ::fputc('*', out
); // Insert word in personal dictionary
471 ::fputs(encoded
.c_str(), out
);
476 void ISpell::accept(WordLangTuple
const & word
)
478 string
const encoded
= to_iconv_encoding(word
.word(), encoding
);
479 if (encoded
.empty()) {
481 _("Could not accept word `%1$s' because it could not be converted to encoding `%2$s'."),
482 word
.word(), from_ascii(encoding
));
485 ::fputc('@', out
); // Accept in this session
486 ::fputs(encoded
.c_str(), out
);
491 docstring
const ISpell::error()