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