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