]> git.lyx.org Git - lyx.git/blob - src/frontends/controllers/ControlSpellchecker.C
Fix bug 1876. Thanks Alfredo. Thanks Felix-Antoine.
[lyx.git] / src / frontends / controllers / ControlSpellchecker.C
1 /**
2  * \file ControlSpellchecker.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Edwin Leuven
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "ControlSpellchecker.h"
14
15 #include "buffer.h"
16 #include "bufferparams.h"
17 #include "BufferView.h"
18 #include "cursor.h"
19 #include "CutAndPaste.h"
20 #include "debug.h"
21 #include "gettext.h"
22 #include "language.h"
23 #include "lyxrc.h"
24 #include "paragraph.h"
25
26 #if defined(USE_ASPELL)
27 # include "aspell_local.h"
28 #elif defined(USE_PSPELL)
29 # include "pspell.h"
30 #endif
31
32 #if defined(USE_ISPELL)
33 # include "ispell.h"
34 #else
35 # include "SpellBase.h"
36 #endif
37
38 #include "support/textutils.h"
39 #include "support/convert.h"
40
41 #include "frontends/Alert.h"
42
43 using std::advance;
44 using std::distance;
45 using std::endl;
46 using std::string;
47
48 namespace lyx {
49
50 using support::bformat;
51 using support::contains;
52
53 namespace frontend {
54
55
56 ControlSpellchecker::ControlSpellchecker(Dialog & parent)
57         : Dialog::Controller(parent),
58           oldval_(0), newvalue_(0), count_(0)
59 {}
60
61
62 ControlSpellchecker::~ControlSpellchecker()
63 {}
64
65
66 namespace {
67
68 SpellBase * getSpeller(BufferParams const & bp)
69 {
70         string lang = (lyxrc.isp_use_alt_lang)
71                       ? lyxrc.isp_alt_lang
72                       : bp.language->code();
73
74 #if defined(USE_ASPELL)
75         if (lyxrc.use_spell_lib)
76                 return new ASpell(bp, lang);
77 #elif defined(USE_PSPELL)
78         if (lyxrc.use_spell_lib)
79                 return new PSpell(bp, lang);
80 #endif
81
82 #if defined(USE_ISPELL)
83         lang = (lyxrc.isp_use_alt_lang) ?
84                 lyxrc.isp_alt_lang : bp.language->lang();
85
86         return new ISpell(bp, lang);
87 #else
88         return new SpellBase;
89 #endif
90 }
91
92 } // namespace anon
93
94
95 bool ControlSpellchecker::initialiseParams(std::string const &)
96 {
97         lyxerr[Debug::GUI] << "Spellchecker::initialiseParams" << endl;
98
99         speller_.reset(getSpeller(kernel().buffer().params()));
100         if (!speller_.get())
101                 return false;
102
103         // reset values to initial
104         oldval_ = 0;
105         newvalue_ = 0;
106         count_ = 0;
107
108         bool const success = speller_->error().empty();
109
110         if (!success) {
111                 Alert::error(_("The spell-checker could not be started"),
112                              speller_->error());
113                 speller_.reset(0);
114         }
115
116         return success;
117 }
118
119
120 void ControlSpellchecker::clearParams()
121 {
122         lyxerr[Debug::GUI] << "Spellchecker::clearParams" << endl;
123         speller_.reset(0);
124 }
125
126
127 namespace {
128
129 bool isLetter(DocIterator const & cur)
130 {
131         return cur.inTexted()
132                 && cur.inset().allowSpellCheck()
133                 && cur.pos() != cur.lastpos()
134                 && (cur.paragraph().isLetter(cur.pos())
135                     // We want to pass the ' and escape chars to ispell
136                     || contains(lyxrc.isp_esc_chars + '\'',
137                                 cur.paragraph().getChar(cur.pos())))
138                 && !isDeletedText(cur.paragraph(), cur.pos());
139 }
140
141
142 WordLangTuple nextWord(DocIterator & cur, ptrdiff_t & progress,
143         BufferParams & bp)
144 {
145         bool inword = false;
146         bool ignoreword = false;
147         string word, lang_code;
148
149         while (cur.depth()) {
150                 if (isLetter(cur)) {
151                         if (!inword) {
152                                 inword = true;
153                                 ignoreword = false;
154                                 word.clear();
155                                 lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
156                         }
157                         // Insets like optional hyphens and ligature
158                         // break are part of a word.
159                         if (!cur.paragraph().isInset(cur.pos())) {
160                                 Paragraph::value_type const c =
161                                         cur.paragraph().getChar(cur.pos());
162                                 word += c;
163                                 if (IsDigit(c))
164                                         ignoreword = true;
165                         }
166                 } else { // !isLetter(cur)
167                         if (inword)
168                                 if (!ignoreword)
169                                         return WordLangTuple(word, lang_code);
170                                 else
171                                         inword = false;
172                 }
173
174                 cur.forwardPos();
175                 ++progress;
176         }
177
178         return WordLangTuple(string(), string());
179 }
180
181 } // namespace anon
182
183
184
185 void ControlSpellchecker::check()
186 {
187         lyxerr[Debug::GUI] << "spell check a word" << endl;
188
189         SpellBase::Result res = SpellBase::OK;
190
191         DocIterator cur = kernel().bufferview()->cursor();
192
193         ptrdiff_t start = 0, total = 0;
194         DocIterator it = DocIterator(kernel().buffer().inset());
195         for (start = 0; it != cur; it.forwardPos())
196                 ++start;
197
198         for (total = start; it; it.forwardPos())
199                 ++total;
200
201         for (; cur && cur.pos() && isLetter(cur); cur.forwardPos())
202                 ++start;
203
204         BufferParams & bufferparams = kernel().buffer().params();
205
206         while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
207                 word_ = nextWord(cur, start, bufferparams);
208
209                 // end of document
210                 if (getWord().empty()) {
211                         showSummary();
212                         return;
213                 }
214
215                 ++count_;
216
217                 // Update slider if and only if value has changed
218                 float progress = total ? float(start)/total : 1;
219                 newvalue_ = int(100.0 * progress);
220                 if (newvalue_!= oldval_) {
221                         lyxerr[Debug::GUI] << "Updating spell progress." << endl;
222                         oldval_ = newvalue_;
223                         // set progress bar
224                         dialog().view().partialUpdate(SPELL_PROGRESSED);
225                 }
226
227                 // speller might be dead ...
228                 if (!checkAlive())
229                         return;
230
231                 res = speller_->check(word_);
232
233                 // ... or it might just be reporting an error
234                 if (!checkAlive())
235                         return;
236         }
237
238         lyxerr[Debug::GUI] << "Found word \"" << getWord() << "\"" << endl;
239
240         int const size = getWord().size();
241         cur.pos() -= size;
242         kernel().bufferview()->putSelectionAt(cur, size, false);
243         // if we used a lfun like in find/replace, dispatch would do
244         // that for us
245         kernel().bufferview()->update();
246
247         // set suggestions
248         if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
249                 lyxerr[Debug::GUI] << "Found a word needing checking." << endl;
250                 dialog().view().partialUpdate(SPELL_FOUND_WORD);
251         }
252 }
253
254
255 bool ControlSpellchecker::checkAlive()
256 {
257         if (speller_->alive() && speller_->error().empty())
258                 return true;
259
260         string message = speller_->error();
261         if (message.empty())
262                 message = _("The spell-checker has died for some reason.\n"
263                          "Maybe it has been killed.");
264
265         dialog().CancelButton();
266
267         Alert::error(_("The spell-checker has failed"), message);
268         return false;
269 }
270
271
272 void ControlSpellchecker::showSummary()
273 {
274         if (!checkAlive() || count_ == 0) {
275                 dialog().CancelButton();
276                 return;
277         }
278
279         string message;
280         if (count_ != 1)
281                 message = bformat(_("%1$d words checked."), count_);
282         else
283                 message = _("One word checked.");
284
285         dialog().CancelButton();
286         Alert::information(_("Spell-checking is complete"), message);
287 }
288
289
290 void ControlSpellchecker::replace(string const & replacement)
291 {
292         lyxerr << "ControlSpellchecker::replace("
293                << replacement << ")" << std::endl;
294         BufferView & bufferview = *kernel().bufferview();
295         cap::replaceWord(bufferview.cursor(), replacement);
296         kernel().buffer().markDirty();
297         bufferview.update();
298         // fix up the count
299         --count_;
300         check();
301 }
302
303
304 void ControlSpellchecker::replaceAll(string const & replacement)
305 {
306         // TODO: add to list
307         replace(replacement);
308 }
309
310
311 void ControlSpellchecker::insert()
312 {
313         speller_->insert(word_);
314         check();
315 }
316
317
318 string const ControlSpellchecker::getSuggestion() const
319 {
320         return speller_->nextMiss();
321 }
322
323
324 string const ControlSpellchecker::getWord() const
325 {
326         return word_.word();
327 }
328
329
330 void ControlSpellchecker::ignoreAll()
331 {
332         speller_->accept(word_);
333         check();
334 }
335
336 } // namespace frontend
337 } // namespace lyx