]> git.lyx.org Git - lyx.git/blob - src/Trans.cpp
Implement LFUN_SPELLING_REMOVE (patch from switt)
[lyx.git] / src / Trans.cpp
1 /**
2  * \file Trans.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Matthias Ettrich
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "Trans.h"
15
16 #include "Buffer.h"
17 #include "BufferView.h"
18 #include "Cursor.h"
19 #include "CutAndPaste.h"
20 #include "Lexer.h"
21 #include "LyXRC.h"
22 #include "Text.h"
23
24 #include "support/debug.h"
25 #include "support/docstream.h"
26 #include "support/FileName.h"
27 #include "support/filetools.h"
28 #include "support/lstrings.h"
29
30 using namespace std;
31 using namespace lyx::support;
32
33 namespace lyx {
34
35 /////////////////////////////////////////////////////////////////////
36 //
37 // TeXAccents
38 //
39 /////////////////////////////////////////////////////////////////////
40
41 /* the names used by TeX and XWindows for deadkeys/accents are not the same
42    so here follows a table to clearify the differences. Please correct this
43    if I got it wrong
44
45    |------------------|------------------|------------------|--------------|
46    |      TeX         |     XWindows     |   \bind/LFUN     | used by intl |
47    |------------------|------------------|------------------|--------------|
48    |    grave         |    grave         |LFUN_ACCENT_GRAVE        | grave
49    |    acute         |    acute         |LFUN_ACCENT_ACUTE        | acute
50    |    circumflex    |    circumflex    |LFUN_ACCENT_CIRCUMFLEX   | circumflex
51    | umlaut/dieresis  |    diaeresis     |LFUN_ACCENT_UMLAUT       | umlaut
52    |    tilde         |    tilde         |LFUN_ACCENT_TILDE        | tilde
53    |    macron        |    maron         |LFUN_ACCENT_MACRON       | macron
54    |    dot           |    abovedot      |LFUN_ACCENT_DOT          | dot
55    |    cedilla       |    cedilla       |LFUN_ACCENT_CEDILLA      | cedilla
56    |    underdot      |                  |LFUN_ACCENT_UNDERDOT     | underdot
57    |    underbar      |                  |LFUN_ACCENT_UNDERBAR     | underbar
58    |    hácek         |    caron         |LFUN_ACCENT_CARON        | caron
59    |    breve         |    breve         |LFUN_ACCENT_BREVE        | breve
60    |    tie           |                  |LFUN_ACCENT_TIE          | tie
61    | Hungarian umlaut |    doubleacute   |LFUN_ACCENT_HUNGARIAN_UMLAUT  | hungarian umlaut
62    |    circle        |    abovering     |LFUN_ACCENT_CIRCLE       | circle
63    |                  |    ogonek        |                  |
64    |                  |    iota          |                  |
65    |                  |    voiced_sound  |                  |
66    |                  | semivoiced_sound |                  |
67    */
68 static TeXAccent lyx_accent_table[] = {
69         {TEX_NOACCENT,   0,      "",                LFUN_NOACTION},
70         {TEX_ACUTE,      0x0301, "acute",           LFUN_ACCENT_ACUTE},
71         {TEX_GRAVE,      0x0300, "grave",           LFUN_ACCENT_GRAVE},
72         {TEX_MACRON,     0x0304, "macron",          LFUN_ACCENT_MACRON},
73         {TEX_TILDE,      0x0303, "tilde",           LFUN_ACCENT_TILDE},
74         {TEX_UNDERBAR,   0x0320, "underbar",        LFUN_ACCENT_UNDERBAR},
75         {TEX_CEDILLA,    0x0327, "cedilla",         LFUN_ACCENT_CEDILLA},
76         {TEX_UNDERDOT,   0x0323, "underdot",        LFUN_ACCENT_UNDERDOT},
77         {TEX_CIRCUMFLEX, 0x0302, "circumflex",      LFUN_ACCENT_CIRCUMFLEX},
78         {TEX_CIRCLE,     0x030a, "circle",          LFUN_ACCENT_CIRCLE},
79         {TEX_TIE,        0x0361, "tie",             LFUN_ACCENT_TIE},
80         {TEX_BREVE,      0x0306, "breve",           LFUN_ACCENT_BREVE},
81         {TEX_CARON,      0x030c, "caron",           LFUN_ACCENT_CARON},
82         // Don't fix this typo for compatibility reasons!
83         {TEX_HUNGUML,    0x030b, "hugarian_umlaut", LFUN_ACCENT_HUNGARIAN_UMLAUT},
84         {TEX_UMLAUT,     0x0308, "umlaut",          LFUN_ACCENT_UMLAUT},
85         {TEX_DOT,        0x0307, "dot",             LFUN_ACCENT_DOT},
86         {TEX_OGONEK,     0x0328, "ogonek",          LFUN_ACCENT_OGONEK}
87 };
88
89
90 TeXAccent get_accent(FuncCode action)
91 {
92         int i = 0;
93         while (i <= TEX_MAX_ACCENT) {
94                 if (lyx_accent_table[i].action == action)
95                         return lyx_accent_table[i];
96                 ++i;
97         }
98         struct TeXAccent temp = { static_cast<tex_accent>(0), 0,
99                                           0, static_cast<FuncCode>(0)};
100         return temp;
101 }
102
103
104 static docstring const doAccent(docstring const & s, tex_accent accent)
105 {
106         if (s.empty())
107                 return docstring(1, lyx_accent_table[accent].ucs4);
108
109         odocstringstream os;
110         os.put(s[0]);
111         os.put(lyx_accent_table[accent].ucs4);
112         if (s.length() > 1) {
113                 if (accent != TEX_TIE || s.length() > 2)
114                         lyxerr << "Warning: Too many characters given for accent "
115                                << lyx_accent_table[accent].name << '.' << endl;
116                 os << s.substr(1);
117         }
118         return normalize_c(os.str());
119 }
120
121
122 static docstring const doAccent(char_type c, tex_accent accent)
123 {
124         return doAccent(docstring(1, c), accent);
125 }
126
127
128
129 /////////////////////////////////////////////////////////////////////
130 //
131 // Trans
132 //
133 /////////////////////////////////////////////////////////////////////
134
135
136 void Trans::insertException(KmodException & exclist, char_type c,
137         docstring const & data, bool flag, tex_accent accent)
138 {
139         Keyexc p;
140         p.c = c;
141         p.data = data;
142         p.combined = flag;
143         p.accent = accent;
144         exclist.insert(exclist.begin(), p);
145         // or just
146         // exclist.push_back(p);
147 }
148
149
150 void Trans::freeException(KmodException & exclist)
151 {
152         exclist.clear();
153 }
154
155
156 void Trans::freeKeymap()
157 {
158         kmod_list_.clear();
159         keymap_.clear();
160 }
161
162
163 bool Trans::isDefined() const
164 {
165         return !name_.empty();
166 }
167
168
169 enum {
170         KCOMB = 1,
171         KMOD,
172         KMAP,
173         KXMOD,
174 };
175
176
177 tex_accent getkeymod(string const &);
178
179
180 void Trans::addDeadkey(tex_accent accent, docstring const & keys)
181 {
182         KmodInfo tmp;
183         tmp.data = keys;
184         tmp.accent = accent;
185         kmod_list_[accent] = tmp;
186
187         for (docstring::size_type i = 0; i < keys.length(); ++i) {
188                 // FIXME This is a hack.
189                 // tmp is no valid UCS4 string, but misused to store the
190                 // accent.
191                 docstring tmp;
192                 tmp += char_type(0);
193                 tmp += char_type(accent);
194                 keymap_[keys[i]] = tmp;
195         }
196 }
197
198
199 int Trans::load(Lexer & lex)
200 {
201         bool error = false;
202
203         while (lex.isOK() && !error) {
204                 switch (lex.lex()) {
205                 case KMOD:
206                 {
207                         LYXERR(Debug::KBMAP, "KMOD:\t" << lex.getString());
208                         if (!lex.next(true))
209                                 return -1;
210
211                         LYXERR(Debug::KBMAP, "key\t`" << lex.getString() << '\'');
212
213                         docstring const keys = lex.getDocString();
214
215                         if (!lex.next(true))
216                                 return -1;
217
218                         LYXERR(Debug::KBMAP, "accent\t`" << lex.getString() << '\'');
219
220                         tex_accent accent = getkeymod(lex.getString());
221
222                         if (accent == TEX_NOACCENT)
223                                 return -1;
224
225 #if 1
226                         // FIXME: This code should be removed...
227                         // But we need to fix up all the kmap files first
228                         // so that this field is not present anymore.
229                         if (!lex.next(true))
230                                 return -1;
231
232                         LYXERR(Debug::KBMAP, "allowed\t`" << lex.getString() << '\'');
233
234                         /* string const allowed = lex.getString(); */
235                         addDeadkey(accent, keys /*, allowed*/);
236 #else
237                         addDeadkey(accent, keys);
238 #endif
239                         break;
240                 }
241                 case KCOMB: {
242                         string str;
243
244                         LYXERR(Debug::KBMAP, "KCOMB:");
245                         if (!lex.next(true))
246                                 return -1;
247
248                         str = lex.getString();
249                         LYXERR(Debug::KBMAP, str);
250
251                         tex_accent accent_1 = getkeymod(str);
252                         if (accent_1 == TEX_NOACCENT)
253                                 return -1;
254
255                         if (!lex.next(true))
256                                 return -1;
257
258                         str = lex.getString();
259                         LYXERR(Debug::KBMAP, str);
260
261                         tex_accent accent_2 = getkeymod(str);
262                         if (accent_2 == TEX_NOACCENT) return -1;
263
264                         map<tex_accent, KmodInfo>::iterator it1 =
265                                 kmod_list_.find(accent_1);
266                         map<tex_accent, KmodInfo>::iterator it2 =
267                                 kmod_list_.find(accent_2);
268                         if (it1 == kmod_list_.end() || it2 == kmod_list_.end())
269                                 return -1;
270
271                         // Find what key accent_2 is on - should
272                         // check about accent_1 also
273                         map<char_type, docstring>::iterator it = keymap_.begin();
274                         map<char_type, docstring>::iterator end = keymap_.end();
275                         for (; it != end; ++it) {
276                                 if (!it->second.empty()
277                                     && it->second[0] == 0
278                                     && it->second[1] == accent_2)
279                                         break;
280                         }
281                         docstring allowed;
282                         if (!lex.next())
283                                 return -1;
284
285                         allowed = lex.getDocString();
286                         LYXERR(Debug::KBMAP, "allowed: " << to_utf8(allowed));
287
288                         insertException(kmod_list_[accent_1].exception_list,
289                                         it->first, allowed, true, accent_2);
290                 }
291                 break;
292                 case KMAP: {
293                         unsigned char key_from;
294
295                         LYXERR(Debug::KBMAP, "KMAP:\t" << lex.getString());
296
297                         if (!lex.next(true))
298                                 return -1;
299
300                         key_from = lex.getString()[0];
301                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
302
303                         if (!lex.next(true))
304                                 return -1;
305
306                         docstring const string_to = lex.getDocString();
307                         keymap_[key_from] = string_to;
308                         LYXERR(Debug::KBMAP, "\t`" << to_utf8(string_to) << '\'');
309                         break;
310                 }
311                 case KXMOD: {
312                         tex_accent accent;
313                         char_type key;
314                         docstring str;
315
316                         LYXERR(Debug::KBMAP, "KXMOD:\t" << lex.getString());
317
318                         if (!lex.next(true))
319                                 return -1;
320
321                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
322                         accent = getkeymod(lex.getString());
323
324                         if (!lex.next(true))
325                                 return -1;
326
327                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
328                         key = lex.getDocString()[0];
329
330                         if (!lex.next(true))
331                                 return -1;
332
333                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
334                         str = lex.getDocString();
335
336                         insertException(kmod_list_[accent].exception_list,
337                                         key, str);
338                         break;
339                 }
340                 case Lexer::LEX_FEOF:
341                         LYXERR(Debug::PARSER, "End of parsing");
342                         break;
343                 default:
344                         lex.printError("ParseKeymapFile: Unknown tag: `$$Token'");
345                         return -1;
346                 }
347         }
348         return 0;
349 }
350
351
352 bool Trans::isAccentDefined(tex_accent accent, KmodInfo & i) const
353 {
354         map<tex_accent, KmodInfo>::const_iterator cit = kmod_list_.find(accent);
355         if (cit == kmod_list_.end())
356                 return false;
357         i = cit->second;
358         return true;
359 }
360
361
362 docstring const Trans::process(char_type c, TransManager & k)
363 {
364         docstring const t = match(c);
365
366         if (t.empty() && c != 0)
367                 return k.normalkey(c);
368
369         if (!t.empty() && t[0] != 0)
370                 return t; //return k.normalkey(c);
371
372         return k.deadkey(c, kmod_list_[static_cast<tex_accent>(t[1])]);
373 }
374
375
376 int Trans::load(string const & language)
377 {
378         LexerKeyword kmapTags[] = {
379                 {"\\kcomb", KCOMB },
380                 { "\\kmap", KMAP },
381                 { "\\kmod", KMOD },
382                 { "\\kxmod", KXMOD }
383         };
384
385         FileName const filename = libFileSearch("kbd", language, "kmap");
386         if (filename.empty())
387                 return -1;
388
389         freeKeymap();
390         Lexer lex(kmapTags);
391         lex.setFile(filename);
392
393         int const res = load(lex);
394
395         if (res == 0)
396                 name_ = language;
397         else
398                 name_.erase();
399
400         return res;
401 }
402
403
404 tex_accent getkeymod(string const & p)
405         /* return modifier - decoded from p and update p */
406 {
407         for (int i = 1; i <= TEX_MAX_ACCENT; ++i) {
408                 LYXERR(Debug::KBMAP, "p = " << p
409                        << ", lyx_accent_table[" << i
410                        << "].name = `" << lyx_accent_table[i].name << '\'');
411
412                 if (lyx_accent_table[i].name
413                      && contains(p, lyx_accent_table[i].name)) {
414                         LYXERR(Debug::KBMAP, "Found it!");
415                         return static_cast<tex_accent>(i);
416                 }
417         }
418         return TEX_NOACCENT;
419 }
420
421
422 /////////////////////////////////////////////////////////////////////
423 //
424 // TransState
425 //
426 /////////////////////////////////////////////////////////////////////
427
428
429 // TransFSMData
430 TransFSMData::TransFSMData()
431 {
432         deadkey_ = deadkey2_ = 0;
433         deadkey_info_.accent = deadkey2_info_.accent = TEX_NOACCENT;
434 }
435
436
437 // TransState
438 char_type const TransState::TOKEN_SEP = 4;
439
440
441 // TransInitState
442 TransInitState::TransInitState()
443 {
444         init_state_ = this;
445 }
446
447
448 docstring const TransInitState::normalkey(char_type c)
449 {
450         docstring res;
451         res = c;
452         return res;
453 }
454
455
456 docstring const TransInitState::deadkey(char_type c, KmodInfo d)
457 {
458         deadkey_ = c;
459         deadkey_info_ = d;
460         currentState = deadkey_state_;
461         return docstring();
462 }
463
464
465 // TransDeadkeyState
466 TransDeadkeyState::TransDeadkeyState()
467 {
468         deadkey_state_ = this;
469 }
470
471
472 docstring const TransDeadkeyState::normalkey(char_type c)
473 {
474         docstring res;
475
476         KmodException::iterator it = deadkey_info_.exception_list.begin();
477         KmodException::iterator end = deadkey_info_.exception_list.end();
478
479         for (; it != end; ++it) {
480                 if (it->c == c) {
481                         res = it->data;
482                         break;
483                 }
484         }
485         if (it == end) {
486                 res = doAccent(c, deadkey_info_.accent);
487         }
488         currentState = init_state_;
489         return res;
490 }
491
492
493 docstring const TransDeadkeyState::deadkey(char_type c, KmodInfo d)
494 {
495         docstring res;
496
497         // Check if the same deadkey was typed twice
498         if (deadkey_ == c) {
499                 res = deadkey_;
500                 deadkey_ = 0;
501                 deadkey_info_.accent = TEX_NOACCENT;
502                 currentState = init_state_;
503                 return res;
504         }
505
506         // Check if it is a combination or an exception
507         KmodException::const_iterator cit = deadkey_info_.exception_list.begin();
508         KmodException::const_iterator end = deadkey_info_.exception_list.end();
509         for (; cit != end; ++cit) {
510                 if (cit->combined == true && cit->accent == d.accent) {
511                         deadkey2_ = c;
512                         deadkey2_info_ = d;
513                         comb_info_ = (*cit);
514                         currentState = combined_state_;
515                         return docstring();
516                 }
517                 if (cit->c == c) {
518                         res = cit->data;
519                         deadkey_ = 0;
520                         deadkey_info_.accent = TEX_NOACCENT;
521                         currentState = init_state_;
522                         return res;
523                 }
524         }
525
526         // Not a combination or an exception.
527         // Output deadkey1 and keep deadkey2
528
529         if (deadkey_!= 0)
530                 res = deadkey_;
531         deadkey_ = c;
532         deadkey_info_ = d;
533         currentState = deadkey_state_;
534         return res;
535 }
536
537
538 TransCombinedState::TransCombinedState()
539 {
540         combined_state_ = this;
541 }
542
543
544 docstring const TransCombinedState::normalkey(char_type c)
545 {
546         docstring const temp = doAccent(c, deadkey2_info_.accent);
547         docstring const res = doAccent(temp, deadkey_info_.accent);
548         currentState = init_state_;
549         return res;
550 }
551
552
553 docstring const TransCombinedState::deadkey(char_type c, KmodInfo d)
554 {
555         // Third key in a row. Output the first one and
556         // reenter with shifted deadkeys
557         docstring res;
558         if (deadkey_ != 0)
559                 res = deadkey_;
560         res += TOKEN_SEP;
561         deadkey_ = deadkey2_;
562         deadkey_info_ = deadkey2_info_;
563         res += deadkey_state_->deadkey(c, d);
564         return res;
565 }
566
567
568 // TransFSM
569 TransFSM::TransFSM()
570         : TransFSMData(), TransInitState(), TransDeadkeyState(), TransCombinedState()
571 {
572         currentState = init_state_;
573 }
574
575
576 // TransManager
577
578 // Initialize static member.
579 Trans TransManager::default_;
580
581
582 TransManager::TransManager()
583         : active_(0)
584 {}
585
586
587 int TransManager::setPrimary(string const & language)
588 {
589         if (t1_.getName() == language)
590                 return 0;
591
592         return t1_.load(language);
593 }
594
595
596 int TransManager::setSecondary(string const & language)
597 {
598         if (t2_.getName() == language)
599                 return 0;
600
601         return t2_.load(language);
602 }
603
604
605 void TransManager::enablePrimary()
606 {
607         if (t1_.isDefined())
608                 active_ = &t1_;
609
610         LYXERR(Debug::KBMAP, "Enabling primary keymap");
611 }
612
613
614 void TransManager::enableSecondary()
615 {
616         if (t2_.isDefined())
617                 active_ = &t2_;
618         LYXERR(Debug::KBMAP, "Enabling secondary keymap");
619 }
620
621
622 void TransManager::disableKeymap()
623 {
624         active_ = &default_;
625         LYXERR(Debug::KBMAP, "Disabling keymap");
626 }
627
628
629 void  TransManager::translateAndInsert(char_type c, Text * text, Cursor & cur)
630 {
631         docstring res = active_->process(c, *this);
632
633         // Process with tokens
634         docstring temp;
635
636         while (res.length() > 0) {
637                 res = split(res, temp, TransState::TOKEN_SEP);
638                 insert(temp, text, cur);
639         }
640 }
641
642
643 void TransManager::insert(docstring const & str, Text * text, Cursor & cur)
644 {
645         for (size_t i = 0, n = str.size(); i != n; ++i)
646                 text->insertChar(cur, str[i]);
647 }
648
649
650 void TransManager::deadkey(char_type c, tex_accent accent, Text * t, Cursor & cur)
651 {
652         if (c == 0 && active_ != &default_) {
653                 // A deadkey was pressed that cannot be printed
654                 // or a accent command was typed in the minibuffer
655                 KmodInfo i;
656                 if (active_->isAccentDefined(accent, i) == true) {
657                         docstring const res = trans_fsm_
658                                 .currentState->deadkey(c, i);
659                         insert(res, t, cur);
660                         return;
661                 }
662         }
663
664         if (active_ == &default_ || c == 0) {
665                 KmodInfo i;
666                 i.accent = accent;
667                 i.data.erase();
668                 docstring res = trans_fsm_.currentState->deadkey(c, i);
669                 insert(res, t, cur);
670         } else {
671                 // Go through the translation
672                 translateAndInsert(c, t, cur);
673         }
674 }
675
676
677 } // namespace lyx