]> git.lyx.org Git - lyx.git/blob - src/Trans.cpp
2f0f317355f5bcb49df054c740b685e9f348af00
[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_PERISPOMENI, 0x0342, "perispomeni",    LFUN_ACCENT_PERISPOMENI},
75         {TEX_UNDERBAR,   0x0320, "underbar",        LFUN_ACCENT_UNDERBAR}, // COMBINING MINUS SIGN BELOW or 0x0331 COMBINING MACRON BELOW ?
76
77         {TEX_CEDILLA,    0x0327, "cedilla",         LFUN_ACCENT_CEDILLA},
78         {TEX_UNDERDOT,   0x0323, "underdot",        LFUN_ACCENT_UNDERDOT},
79         {TEX_CIRCUMFLEX, 0x0302, "circumflex",      LFUN_ACCENT_CIRCUMFLEX},
80         {TEX_CIRCLE,     0x030a, "circle",          LFUN_ACCENT_CIRCLE},
81         {TEX_TIE,        0x0361, "tie",             LFUN_ACCENT_TIE},
82         {TEX_BREVE,      0x0306, "breve",           LFUN_ACCENT_BREVE},
83         {TEX_CARON,      0x030c, "caron",           LFUN_ACCENT_CARON},
84         // Don't fix this typo for compatibility reasons!
85         {TEX_HUNGUML,    0x030b, "hugarian_umlaut", LFUN_ACCENT_HUNGARIAN_UMLAUT},
86         {TEX_UMLAUT,     0x0308, "umlaut",          LFUN_ACCENT_UMLAUT},
87         {TEX_DOT,        0x0307, "dot",             LFUN_ACCENT_DOT},
88         {TEX_OGONEK,     0x0328, "ogonek",          LFUN_ACCENT_OGONEK}
89 };
90
91
92 TeXAccent get_accent(FuncCode action)
93 {
94         int i = 0;
95         while (i <= TEX_MAX_ACCENT) {
96                 if (lyx_accent_table[i].action == action)
97                         return lyx_accent_table[i];
98                 ++i;
99         }
100         struct TeXAccent temp = { static_cast<tex_accent>(0), 0,
101                                           nullptr, static_cast<FuncCode>(0)};
102         return temp;
103 }
104
105
106 static docstring const doAccent(docstring const & s, tex_accent accent)
107 {
108         if (s.empty())
109                 return docstring(1, lyx_accent_table[accent].ucs4);
110
111         odocstringstream os;
112         os.put(s[0]);
113         os.put(lyx_accent_table[accent].ucs4);
114         if (s.length() > 1) {
115                 if (accent != TEX_TIE || s.length() > 2)
116                         lyxerr << "Warning: Too many characters given for accent "
117                                << lyx_accent_table[accent].name << '.' << endl;
118                 os << s.substr(1);
119         }
120         return normalize_c(os.str());
121 }
122
123
124 static docstring const doAccent(char_type c, tex_accent accent)
125 {
126         return doAccent(docstring(1, c), accent);
127 }
128
129
130
131 /////////////////////////////////////////////////////////////////////
132 //
133 // Trans
134 //
135 /////////////////////////////////////////////////////////////////////
136
137
138 void Trans::insertException(KmodException & exclist, char_type c,
139         docstring const & data, bool flag, tex_accent accent)
140 {
141         Keyexc p;
142         p.c = c;
143         p.data = data;
144         p.combined = flag;
145         p.accent = accent;
146         exclist.insert(exclist.begin(), p);
147         // or just
148         // exclist.push_back(p);
149 }
150
151
152 void Trans::freeException(KmodException & exclist)
153 {
154         exclist.clear();
155 }
156
157
158 void Trans::freeKeymap()
159 {
160         kmod_list_.clear();
161         keymap_.clear();
162 }
163
164
165 bool Trans::isDefined() const
166 {
167         return !name_.empty();
168 }
169
170
171 enum {
172         KCOMB = 1,
173         KMOD,
174         KMAP,
175         KXMOD
176 };
177
178
179 tex_accent getkeymod(string const &);
180
181
182 void Trans::addDeadkey(tex_accent accent, docstring const & keys)
183 {
184         KmodInfo tmp;
185         tmp.data = keys;
186         tmp.accent = accent;
187         kmod_list_[accent] = tmp;
188
189         for (docstring::size_type i = 0; i < keys.length(); ++i) {
190                 // FIXME This is a hack.
191                 // tmp is no valid UCS4 string, but misused to store the
192                 // accent.
193                 docstring tmpd;
194                 tmpd += char_type(0);
195                 tmpd += char_type(accent);
196                 keymap_[keys[i]] = tmpd;
197         }
198 }
199
200
201 int Trans::load(Lexer & lex)
202 {
203         while (lex.isOK()) {
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
282                         // could not find accent2 on a key -- this should not happen.
283                         if (it == end)
284                                 return -1;
285
286                         docstring allowed;
287                         if (!lex.next())
288                                 return -1;
289                         allowed = lex.getDocString();
290                         LYXERR(Debug::KBMAP, "allowed: " << to_utf8(allowed));
291
292                         insertException(kmod_list_[accent_1].exception_list,
293                                         it->first, allowed, true, accent_2);
294                 }
295                 break;
296                 case KMAP: {
297                         unsigned char key_from;
298
299                         LYXERR(Debug::KBMAP, "KMAP:\t" << lex.getString());
300
301                         if (!lex.next(true))
302                                 return -1;
303
304                         key_from = static_cast<unsigned char>(lex.getString()[0]);
305                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
306
307                         if (!lex.next(true))
308                                 return -1;
309
310                         docstring const string_to = lex.getDocString();
311                         keymap_[key_from] = string_to;
312                         LYXERR(Debug::KBMAP, "\t`" << to_utf8(string_to) << '\'');
313                         break;
314                 }
315                 case KXMOD: {
316                         tex_accent accent;
317                         char_type key;
318                         docstring str;
319
320                         LYXERR(Debug::KBMAP, "KXMOD:\t" << lex.getString());
321
322                         if (!lex.next(true))
323                                 return -1;
324
325                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
326                         accent = getkeymod(lex.getString());
327
328                         if (!lex.next(true))
329                                 return -1;
330
331                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
332                         key = lex.getDocString()[0];
333
334                         if (!lex.next(true))
335                                 return -1;
336
337                         LYXERR(Debug::KBMAP, "\t`" << lex.getString() << '\'');
338                         str = lex.getDocString();
339
340                         insertException(kmod_list_[accent].exception_list,
341                                         key, str);
342                         break;
343                 }
344                 case Lexer::LEX_FEOF:
345                         LYXERR(Debug::PARSER, "End of parsing");
346                         break;
347                 default:
348                         lex.printError("ParseKeymapFile: Unknown tag: `$$Token'");
349                         return -1;
350                 }
351         }
352         return 0;
353 }
354
355
356 bool Trans::isAccentDefined(tex_accent accent, KmodInfo & i) const
357 {
358         map<tex_accent, KmodInfo>::const_iterator cit = kmod_list_.find(accent);
359         if (cit == kmod_list_.end())
360                 return false;
361         i = cit->second;
362         return true;
363 }
364
365
366 docstring const Trans::process(char_type c, TransManager & k)
367 {
368         docstring const t = match(c);
369
370         if (t.empty() && c != 0)
371                 return k.normalkey(c);
372
373         if (!t.empty() && t[0] != 0)
374                 return t; //return k.normalkey(c);
375
376         return k.deadkey(c, kmod_list_[static_cast<tex_accent>(t[1])]);
377 }
378
379
380 int Trans::load(string const & language)
381 {
382         LexerKeyword kmapTags[] = {
383                 {"\\kcomb", KCOMB },
384                 { "\\kmap", KMAP },
385                 { "\\kmod", KMOD },
386                 { "\\kxmod", KXMOD }
387         };
388
389         FileName const filename = libFileSearch("kbd", language, "kmap");
390         if (filename.empty())
391                 return -1;
392
393         freeKeymap();
394         Lexer lex(kmapTags);
395         lex.setFile(filename);
396
397         int const res = load(lex);
398
399         if (res == 0)
400                 name_ = language;
401         else
402                 name_.erase();
403
404         return res;
405 }
406
407
408 tex_accent getkeymod(string const & p)
409         /* return modifier - decoded from p and update p */
410 {
411         for (int i = 1; i <= TEX_MAX_ACCENT; ++i) {
412                 LYXERR(Debug::KBMAP, "p = " << p
413                        << ", lyx_accent_table[" << i
414                        << "].name = `" << lyx_accent_table[i].name << '\'');
415
416                 if (lyx_accent_table[i].name
417                      && contains(p, lyx_accent_table[i].name)) {
418                         LYXERR(Debug::KBMAP, "Found it!");
419                         return static_cast<tex_accent>(i);
420                 }
421         }
422         return TEX_NOACCENT;
423 }
424
425
426 /////////////////////////////////////////////////////////////////////
427 //
428 // TransState
429 //
430 /////////////////////////////////////////////////////////////////////
431
432
433 // TransFSMData
434 TransFSMData::TransFSMData() : deadkey_(0), deadkey2_(0), init_state_(nullptr),
435         deadkey_state_(nullptr), combined_state_(nullptr), currentState(nullptr)
436 {
437 }
438
439
440 // TransState
441 char_type const TransState::TOKEN_SEP = 4;
442
443
444 // TransInitState
445 TransInitState::TransInitState()
446 {
447         init_state_ = this;
448 }
449
450
451 docstring const TransInitState::normalkey(char_type c)
452 {
453         docstring res;
454         res = c;
455         return res;
456 }
457
458
459 docstring const TransInitState::deadkey(char_type c, KmodInfo d)
460 {
461         deadkey_ = c;
462         deadkey_info_ = d;
463         currentState = deadkey_state_;
464         return docstring();
465 }
466
467
468 // TransDeadkeyState
469 TransDeadkeyState::TransDeadkeyState()
470 {
471         deadkey_state_ = this;
472 }
473
474
475 docstring const TransDeadkeyState::normalkey(char_type c)
476 {
477         docstring res;
478
479         KmodException::iterator it = deadkey_info_.exception_list.begin();
480         KmodException::iterator end = deadkey_info_.exception_list.end();
481
482         for (; it != end; ++it) {
483                 if (it->c == c) {
484                         res = it->data;
485                         break;
486                 }
487         }
488         if (it == end) {
489                 res = doAccent(c, deadkey_info_.accent);
490         }
491         currentState = init_state_;
492         return res;
493 }
494
495
496 docstring const TransDeadkeyState::deadkey(char_type c, KmodInfo d)
497 {
498         docstring res;
499
500         // Check if the same deadkey was typed twice
501         if (deadkey_ == c) {
502                 res = deadkey_;
503                 deadkey_ = 0;
504                 deadkey_info_.accent = TEX_NOACCENT;
505                 currentState = init_state_;
506                 return res;
507         }
508
509         // Check if it is a combination or an exception
510         KmodException::const_iterator cit = deadkey_info_.exception_list.begin();
511         KmodException::const_iterator end = deadkey_info_.exception_list.end();
512         for (; cit != end; ++cit) {
513                 if (cit->combined && cit->accent == d.accent) {
514                         deadkey2_ = c;
515                         deadkey2_info_ = d;
516                         comb_info_ = (*cit);
517                         currentState = combined_state_;
518                         return docstring();
519                 }
520                 if (cit->c == c) {
521                         res = cit->data;
522                         deadkey_ = 0;
523                         deadkey_info_.accent = TEX_NOACCENT;
524                         currentState = init_state_;
525                         return res;
526                 }
527         }
528
529         // Not a combination or an exception.
530         // Output deadkey1 and keep deadkey2
531
532         if (deadkey_!= 0)
533                 res = deadkey_;
534         deadkey_ = c;
535         deadkey_info_ = d;
536         currentState = deadkey_state_;
537         return res;
538 }
539
540
541 TransCombinedState::TransCombinedState()
542 {
543         combined_state_ = this;
544 }
545
546
547 docstring const TransCombinedState::normalkey(char_type c)
548 {
549         docstring const temp = doAccent(c, deadkey2_info_.accent);
550         docstring const res = doAccent(temp, deadkey_info_.accent);
551         currentState = init_state_;
552         return res;
553 }
554
555
556 docstring const TransCombinedState::deadkey(char_type c, KmodInfo d)
557 {
558         // Third key in a row. Output the first one and
559         // reenter with shifted deadkeys
560         docstring res;
561         if (deadkey_ != 0)
562                 res = deadkey_;
563         res += TOKEN_SEP;
564         deadkey_ = deadkey2_;
565         deadkey_info_ = deadkey2_info_;
566         res += deadkey_state_->deadkey(c, d);
567         return res;
568 }
569
570
571 // TransFSM
572 TransFSM::TransFSM()
573         : TransFSMData(), TransInitState(), TransDeadkeyState(), TransCombinedState()
574 {
575         currentState = init_state_;
576 }
577
578
579 // TransManager
580
581 // Initialize static member.
582 Trans TransManager::default_;
583
584
585 TransManager::TransManager()
586         : active_(&default_)
587 {}
588
589
590 int TransManager::setPrimary(string const & language)
591 {
592         if (t1_.getName() == language)
593                 return 0;
594
595         return t1_.load(language);
596 }
597
598
599 int TransManager::setSecondary(string const & language)
600 {
601         if (t2_.getName() == language)
602                 return 0;
603
604         return t2_.load(language);
605 }
606
607
608 void TransManager::enablePrimary()
609 {
610         if (t1_.isDefined())
611                 active_ = &t1_;
612
613         LYXERR(Debug::KBMAP, "Enabling primary keymap");
614 }
615
616
617 void TransManager::enableSecondary()
618 {
619         if (t2_.isDefined())
620                 active_ = &t2_;
621         LYXERR(Debug::KBMAP, "Enabling secondary keymap");
622 }
623
624
625 void TransManager::disableKeymap()
626 {
627         active_ = &default_;
628         LYXERR(Debug::KBMAP, "Disabling keymap");
629 }
630
631
632 void  TransManager::translateAndInsert(char_type c, Text * text, Cursor & cur)
633 {
634         docstring res = active_->process(c, *this);
635
636         // Process with tokens
637         docstring temp;
638
639         while (res.length() > 0) {
640                 res = split(res, temp, TransState::TOKEN_SEP);
641                 insert(temp, text, cur);
642         }
643 }
644
645
646 void TransManager::insert(docstring const & str, Text * text, Cursor & cur)
647 {
648         for (size_t i = 0, n = str.size(); i != n; ++i)
649                 text->insertChar(cur, str[i]);
650 }
651
652
653 void TransManager::deadkey(char_type c, tex_accent accent, Text * t, Cursor & cur)
654 {
655         if (c == 0 && active_ != &default_) {
656                 // A deadkey was pressed that cannot be printed
657                 // or a accent command was typed in the minibuffer
658                 KmodInfo i;
659                 if (active_->isAccentDefined(accent, i)) {
660                         docstring const res = trans_fsm_
661                                 .currentState->deadkey(c, i);
662                         insert(res, t, cur);
663                         return;
664                 }
665         }
666
667         if (active_ == &default_ || c == 0) {
668                 KmodInfo i;
669                 i.accent = accent;
670                 i.data.erase();
671                 docstring res = trans_fsm_.currentState->deadkey(c, i);
672                 insert(res, t, cur);
673         } else {
674                 // Go through the translation
675                 translateAndInsert(c, t, cur);
676         }
677 }
678
679
680 } // namespace lyx