1 // Copyright 2022 Garrett D'Amore
2 //
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE or https://www.boost.org/LICENSE_1_0.txt)
5 
6 module dcell.parser;
7 
8 import core.time;
9 import std.ascii;
10 import std.string;
11 import std.utf;
12 
13 import dcell.key;
14 import dcell.mouse;
15 import dcell.event;
16 import dcell.termcap;
17 
18 package:
19 
20 struct KeyCode
21 {
22     Key key;
23     Modifiers mod;
24 }
25 
26 struct ParseKeys
27 {
28 
29     immutable bool[Key] exist;
30     immutable KeyCode[string] keys;
31     string pasteStart;
32     string pasteEnd;
33 
34     this(const Termcap* caps)
35     {
36 
37         bool[Key] ex;
38         KeyCode[string] kc;
39 
40         void addKey(Key key, string val, Modifiers mod = Modifiers.none, Key replace = cast(Key) 0)
41         {
42             if (val == "")
43                 return;
44             if ((val !in kc) || kc[val].key == replace)
45             {
46                 ex[key] = true;
47                 kc[val] = KeyCode(key, mod);
48             }
49         }
50 
51         void addXTermKey(Key key, string val)
52         {
53             if (val.length > 2 && val[0] == '\x1b' && val[1] == '[' && val[$ - 1] == '~')
54             {
55                 // These suffixes are calculated assuming Xterm style modifier suffixes.
56                 // Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for
57                 // more information (specifically "PC-Style Function Keys").
58                 val = val[0 .. $ - 1]; // drop trailing ~
59                 addKey(key, val ~ ";2~", Modifiers.shift, cast(Key)(key + 12));
60                 addKey(key, val ~ ";3~", Modifiers.alt, cast(Key)(key + 48));
61                 addKey(key, val ~ ";4~", Modifiers.alt | Modifiers.shift, cast(Key)(key + 60));
62                 addKey(key, val ~ ";5~", Modifiers.ctrl, cast(Key)(key + 24));
63                 addKey(key, val ~ ";6~", Modifiers.ctrl | Modifiers.shift, cast(Key)(key + 36));
64                 addKey(key, val ~ ";7~", Modifiers.alt | Modifiers.ctrl);
65                 addKey(key, val ~ ";8~", Modifiers.shift | Modifiers.alt | Modifiers.ctrl);
66                 addKey(key, val ~ ";9~", Modifiers.meta);
67                 addKey(key, val ~ ";10~", Modifiers.meta | Modifiers.shift);
68                 addKey(key, val ~ ";11~", Modifiers.meta | Modifiers.alt);
69                 addKey(key, val ~ ";12~", Modifiers.meta | Modifiers.shift | Modifiers.alt);
70                 addKey(key, val ~ ";13~", Modifiers.meta | Modifiers.ctrl);
71                 addKey(key, val ~ ";14~", Modifiers.meta | Modifiers.ctrl | Modifiers.shift);
72                 addKey(key, val ~ ";15~", Modifiers.meta | Modifiers.ctrl | Modifiers.alt);
73                 addKey(key, val ~ ";16~",
74                     Modifiers.meta | Modifiers.ctrl | Modifiers.shift | Modifiers.alt);
75             }
76             else if (val.length == 3 && val[0] == '\x1b' && val[1] == 'O')
77             {
78                 val = val[2 .. $];
79                 addKey(key, "\x1b[1;2" ~ val, Modifiers.shift, cast(Key)(key + 12));
80                 addKey(key, "\x1b[1;3" ~ val, Modifiers.alt, cast(Key)(key + 48));
81                 addKey(key, "\x1b[1;5" ~ val, Modifiers.ctrl, cast(Key)(key + 24));
82                 addKey(key, "\x1b[1;6" ~ val, Modifiers.ctrl | Modifiers.shift, cast(Key)(key + 36));
83                 addKey(key, "\x1b[1;4" ~ val, Modifiers.alt | Modifiers.shift, cast(Key)(key + 60));
84                 addKey(key, "\x1b[1;7" ~ val, Modifiers.alt | Modifiers.ctrl);
85                 addKey(key, "\x1b[1;8" ~ val, Modifiers.shift | Modifiers.alt | Modifiers.ctrl);
86                 addKey(key, "\x1b[1;9" ~ val, Modifiers.meta,);
87                 addKey(key, "\x1b[1;10" ~ val, Modifiers.meta | Modifiers.shift);
88                 addKey(key, "\x1b[1;11" ~ val, Modifiers.meta | Modifiers.alt);
89                 addKey(key, "\x1b[1;12" ~ val, Modifiers.meta | Modifiers.alt | Modifiers.shift);
90                 addKey(key, "\x1b[1;13" ~ val, Modifiers.meta | Modifiers.ctrl);
91                 addKey(key, "\x1b[1;14" ~ val, Modifiers.meta | Modifiers.ctrl | Modifiers.shift);
92                 addKey(key, "\x1b[1;15" ~ val, Modifiers.meta | Modifiers.ctrl | Modifiers.alt);
93                 addKey(key, "\x1b[1;16" ~ val,
94                     Modifiers.meta | Modifiers.ctrl | Modifiers.alt | Modifiers.shift);
95             }
96         }
97 
98         void addXTermKeys(const Termcap* caps)
99         {
100             if (caps.keyShfRight != "\x1b[1;2C") // does this look "xtermish"?
101                 return;
102             addXTermKey(Key.right, caps.keyRight);
103             addXTermKey(Key.left, caps.keyLeft);
104             addXTermKey(Key.up, caps.keyUp);
105             addXTermKey(Key.down, caps.keyDown);
106             addXTermKey(Key.insert, caps.keyInsert);
107             addXTermKey(Key.del, caps.keyDelete);
108             addXTermKey(Key.pgUp, caps.keyPgUp);
109             addXTermKey(Key.pgDn, caps.keyPgDn);
110             addXTermKey(Key.home, caps.keyHome);
111             addXTermKey(Key.end, caps.keyEnd);
112             addXTermKey(Key.f1, caps.keyF1);
113             addXTermKey(Key.f2, caps.keyF2);
114             addXTermKey(Key.f3, caps.keyF3);
115             addXTermKey(Key.f4, caps.keyF4);
116             addXTermKey(Key.f5, caps.keyF5);
117             addXTermKey(Key.f6, caps.keyF6);
118             addXTermKey(Key.f7, caps.keyF7);
119             addXTermKey(Key.f8, caps.keyF8);
120             addXTermKey(Key.f9, caps.keyF9);
121             addXTermKey(Key.f10, caps.keyF10);
122             addXTermKey(Key.f11, caps.keyF11);
123             addXTermKey(Key.f12, caps.keyF12);
124         }
125 
126         void addCtrlKeys()
127         {
128             // we look briefly at all the keyCodes we
129             // have, to find their starting character.
130             // the vast majority of these will be escape.
131             bool[char] initials;
132             foreach (esc, _; kc)
133             {
134                 if (esc != "")
135                     initials[esc[0]] = true;
136             }
137             // Add key mappings for control keys.
138             for (char i = 0; i < ' '; i++)
139             {
140 
141                 // If this is starting character (typically esc) of other sequences,
142                 // then do not set up the fast path mapping for it.
143                 // We need to let the read do the whole timeout thing.
144                 if (i in initials)
145                     continue;
146 
147                 Key k = cast(Key) i;
148                 ex[k] = true;
149                 switch (k)
150                 {
151                 case Key.backspace, Key.tab, Key.esc, Key.enter:
152                     // these are directly typeable
153                     kc["" ~ i] = KeyCode(k, Modifiers.none);
154                     break;
155                 default:
156                     // these are generally represented as a control sequence
157                     kc["" ~ i] = KeyCode(k, Modifiers.ctrl);
158                     break;
159                 }
160             }
161 
162         }
163 
164         addKey(Key.backspace, caps.keyBackspace);
165         addKey(Key.f1, caps.keyF1);
166         addKey(Key.f2, caps.keyF2);
167         addKey(Key.f3, caps.keyF3);
168         addKey(Key.f4, caps.keyF4);
169         addKey(Key.f5, caps.keyF5);
170         addKey(Key.f6, caps.keyF6);
171         addKey(Key.f7, caps.keyF7);
172         addKey(Key.f8, caps.keyF8);
173         addKey(Key.f9, caps.keyF9);
174         addKey(Key.f10, caps.keyF10);
175         addKey(Key.f11, caps.keyF11);
176         addKey(Key.f12, caps.keyF12);
177         addKey(Key.f13, caps.keyF13);
178         addKey(Key.f14, caps.keyF14);
179         addKey(Key.f15, caps.keyF15);
180         addKey(Key.f16, caps.keyF16);
181         addKey(Key.f17, caps.keyF17);
182         addKey(Key.f18, caps.keyF18);
183         addKey(Key.f19, caps.keyF19);
184         addKey(Key.f20, caps.keyF20);
185         addKey(Key.f21, caps.keyF21);
186         addKey(Key.f22, caps.keyF22);
187         addKey(Key.f23, caps.keyF23);
188         addKey(Key.f24, caps.keyF24);
189         addKey(Key.f25, caps.keyF25);
190         addKey(Key.f26, caps.keyF26);
191         addKey(Key.f27, caps.keyF27);
192         addKey(Key.f28, caps.keyF28);
193         addKey(Key.f29, caps.keyF29);
194         addKey(Key.f30, caps.keyF30);
195         addKey(Key.f31, caps.keyF31);
196         addKey(Key.f32, caps.keyF32);
197         addKey(Key.f33, caps.keyF33);
198         addKey(Key.f34, caps.keyF34);
199         addKey(Key.f35, caps.keyF35);
200         addKey(Key.f36, caps.keyF36);
201         addKey(Key.f37, caps.keyF37);
202         addKey(Key.f38, caps.keyF38);
203         addKey(Key.f39, caps.keyF39);
204         addKey(Key.f40, caps.keyF40);
205         addKey(Key.f41, caps.keyF41);
206         addKey(Key.f42, caps.keyF42);
207         addKey(Key.f43, caps.keyF43);
208         addKey(Key.f44, caps.keyF44);
209         addKey(Key.f45, caps.keyF45);
210         addKey(Key.f46, caps.keyF46);
211         addKey(Key.f47, caps.keyF47);
212         addKey(Key.f48, caps.keyF48);
213         addKey(Key.f49, caps.keyF49);
214         addKey(Key.f50, caps.keyF50);
215         addKey(Key.f51, caps.keyF51);
216         addKey(Key.f52, caps.keyF52);
217         addKey(Key.f53, caps.keyF53);
218         addKey(Key.f54, caps.keyF54);
219         addKey(Key.f55, caps.keyF55);
220         addKey(Key.f56, caps.keyF56);
221         addKey(Key.f57, caps.keyF57);
222         addKey(Key.f58, caps.keyF58);
223         addKey(Key.f59, caps.keyF59);
224         addKey(Key.f60, caps.keyF60);
225         addKey(Key.f61, caps.keyF61);
226         addKey(Key.f62, caps.keyF62);
227         addKey(Key.f63, caps.keyF63);
228         addKey(Key.f64, caps.keyF64);
229         addKey(Key.insert, caps.keyInsert);
230         addKey(Key.del, caps.keyDelete);
231         addKey(Key.home, caps.keyHome);
232         addKey(Key.end, caps.keyEnd);
233         addKey(Key.up, caps.keyUp);
234         addKey(Key.down, caps.keyDown);
235         addKey(Key.left, caps.keyLeft);
236         addKey(Key.right, caps.keyRight);
237         addKey(Key.pgUp, caps.keyPgUp);
238         addKey(Key.pgDn, caps.keyPgDn);
239         addKey(Key.help, caps.keyHelp);
240         addKey(Key.print, caps.keyPrint);
241         addKey(Key.cancel, caps.keyCancel);
242         addKey(Key.exit, caps.keyExit);
243         addKey(Key.backtab, caps.keyBacktab);
244 
245         addKey(Key.right, caps.keyShfRight, Modifiers.shift);
246         addKey(Key.left, caps.keyShfLeft, Modifiers.shift);
247         addKey(Key.up, caps.keyShfUp, Modifiers.shift);
248         addKey(Key.down, caps.keyShfDown, Modifiers.shift);
249         addKey(Key.home, caps.keyShfHome, Modifiers.shift);
250         addKey(Key.end, caps.keyShfEnd, Modifiers.shift);
251         addKey(Key.pgUp, caps.keyShfPgUp, Modifiers.shift);
252         addKey(Key.pgDn, caps.keyShfPgDn, Modifiers.shift);
253 
254         addKey(Key.right, caps.keyCtrlRight, Modifiers.ctrl);
255         addKey(Key.left, caps.keyCtrlLeft, Modifiers.ctrl);
256         addKey(Key.up, caps.keyCtrlUp, Modifiers.ctrl);
257         addKey(Key.down, caps.keyCtrlDown, Modifiers.ctrl);
258         addKey(Key.home, caps.keyCtrlHome, Modifiers.ctrl);
259         addKey(Key.end, caps.keyCtrlEnd, Modifiers.ctrl);
260 
261         // Sadly, xterm handling of keycodes is somewhat erratic.  In
262         // particular, different codes are sent depending on application
263         // mode is in use or not, and the entries for many of these are
264         // simply absent from terminfo on many systems.  So we insert
265         // a number of escape sequences if they are not already used, in
266         // order to have the widest correct usage.  Note that prepareKey
267         // will not inject codes if the escape sequence is already known.
268         // We also only do this for terminals that have the application
269         // mode present.
270 
271         if (caps.enterKeypad != "")
272         {
273             addKey(Key.up, "\x1b[A");
274             addKey(Key.down, "\x1b[B");
275             addKey(Key.right, "\x1b[C");
276             addKey(Key.left, "\x1b[D");
277             addKey(Key.end, "\x1b[F");
278             addKey(Key.home, "\x1b[H");
279             addKey(Key.del2, "\x1b[3~");
280             addKey(Key.home, "\x1b[1~");
281             addKey(Key.end, "\x1b[4~");
282             addKey(Key.pgUp, "\x1b[5~");
283             addKey(Key.pgDn, "\x1b[6~");
284 
285             // Application mode
286             addKey(Key.up, "\x1bOA");
287             addKey(Key.down, "\x1bOB");
288             addKey(Key.right, "\x1bOC");
289             addKey(Key.left, "\x1bOD");
290             addKey(Key.home, "\x1bOH");
291         }
292 
293         addXTermKeys(caps);
294 
295         pasteStart = caps.pasteStart;
296         pasteEnd = caps.pasteEnd;
297 
298         addCtrlKeys(); // do this one last
299 
300         // if DELETE didn't make it at 0x7F, add it - seems to be missing
301         // from some of the sequences.
302         addKey(Key.del2, "\x7F");
303 
304         exist = cast(immutable(bool[Key])) ex;
305         keys = cast(immutable(KeyCode[string])) kc;
306 
307     }
308 
309     bool hasKey(Key k) const pure
310     {
311         if (k == Key.rune)
312         {
313             return true;
314         }
315         return (k in exist ? true : false);
316     }
317 }
318 
319 class Parser
320 {
321 
322     this(const ParseKeys pk)
323     {
324         keyExist = pk.exist;
325         keyCodes = pk.keys;
326         pasteStart = pk.pasteStart;
327         pasteEnd = pk.pasteEnd;
328     }
329 
330     Event[] events() pure
331     {
332         auto res = evs;
333         evs = null;
334         return cast(Event[]) res;
335     }
336 
337     bool parse(string b)
338     {
339         auto now = MonoTime.currTime();
340         if (b.length != 0)
341         {
342             // if we are adding to it, restart the timer
343             keyStart = now;
344         }
345         buf ~= b;
346 
347         while (buf.length != 0)
348         {
349             partial = false;
350 
351             // we have to parse the paste bit first
352             if (parsePaste() || parseRune() || parseFnKey() || parseSgrMouse() || parseLegacyMouse())
353             {
354                 keyStart = now;
355                 continue;
356             }
357             auto expire = ((now - keyStart) > seqTime);
358 
359             if (!partial || expire)
360             {
361                 if (buf[0] == '\x1b')
362                 {
363                     if (buf.length == 1)
364                     {
365                         evs ~= newKeyEvent(Key.esc);
366                         escaped = false;
367                     }
368                     else
369                     {
370                         escaped = true;
371                     }
372                     buf = buf[1 .. $];
373                     keyStart = now;
374                     continue;
375                 }
376                 // no matches or timeout waiting for data, yank off the first byte
377                 evs ~= newKeyEvent(Key.rune, buf[0], escaped ? Modifiers.alt : Modifiers.none);
378                 escaped = false;
379                 keyStart = now;
380                 continue;
381             }
382             // we must have partial data, so wait and come back in a bit
383             return false;
384         }
385 
386         return true;
387     }
388 
389     bool empty() const pure
390     {
391         return buf.length == 0;
392     }
393 
394 private:
395 
396     bool escaped;
397     ubyte[] buf;
398     Event[] evs;
399     const KeyCode[string] keyCodes;
400     const bool[Key] keyExist;
401     bool partial; // record partially parsed sequences
402     MonoTime keyStart; // when the timer started
403     Duration seqTime = msecs(50); // time to fully decode a partial sequence
404     bool buttonDown; // true if buttons were down
405     bool wasButton; // true if we saw a button press for recent mouse event
406     bool pasting;
407     MonoTime pasteTime;
408     dstring pasteBuf;
409     string pasteStart;
410     string pasteEnd;
411 
412     Event newKeyEvent(Key k, dchar dch = 0, Modifiers mod = Modifiers.none)
413     {
414         Event ev = {
415             type: EventType.key, when: MonoTime.currTime(), key: {
416                 key: k, ch: dch, mod: mod
417             }
418         };
419         return ev;
420     }
421 
422     // NB: it is possible for x and y to be outside the current coordinates
423     // (happens for click drag for example).  Consumer of the event should clip
424     // the coordinates as needed.
425     Event newMouseEvent(int x, int y, int btn)
426     {
427         Event ev = {
428             type: EventType.mouse, when: MonoTime.currTime, mouse: {
429                 pos: Coord(x, y)
430             }
431         };
432 
433         // Mouse wheel has bit 6 set, no release events.  It should be noted
434         // that wheel events are sometimes misdelivered as mouse button events
435         // during a click-drag, so we debounce these, considering them to be
436         // button press events unless we see an intervening release event.
437 
438         switch (btn & 0x43)
439         {
440         case 0:
441             ev.mouse.btn = Buttons.button1;
442             wasButton = true;
443             break;
444         case 1:
445             ev.mouse.btn = Buttons.button3;
446             wasButton = true;
447             break;
448         case 2:
449             ev.mouse.btn = Buttons.button2;
450             wasButton = true;
451             break;
452         case 3:
453             ev.mouse.btn = Buttons.none;
454             wasButton = false;
455             break;
456         case 0x40:
457             ev.mouse.btn = wasButton ? Buttons.button1 : Buttons.wheelUp;
458             break;
459         case 0x41:
460             ev.mouse.btn = wasButton ? Buttons.button2 : Buttons.wheelDown;
461             break;
462         default:
463             break;
464         }
465         if (btn & 0x4)
466             ev.mouse.mod |= Modifiers.shift;
467         if (btn & 0x8)
468             ev.mouse.mod |= Modifiers.alt;
469         if (btn & 0x10)
470             ev.mouse.mod |= Modifiers.ctrl;
471         return ev;
472     }
473 
474     Event newPasteEvent(dstring buffer)
475     {
476         Event ev = {
477             type: EventType.paste, when: MonoTime.currTime(), paste: {
478                 content: buffer
479             }
480         };
481         return ev;
482 
483     }
484 
485     bool parseSequence(string seq)
486     {
487         if (startsWith(buf, seq))
488         {
489             buf = buf[seq.length .. $]; // yank the sequence
490             return true;
491         }
492         if (startsWith(seq, buf))
493         {
494             partial = true;
495         }
496         return false;
497     }
498 
499     bool parsePaste()
500     {
501         bool matches;
502 
503         if (pasteStart == "")
504         {
505             return false;
506         }
507 
508         auto now = MonoTime.currTime();
509         if (!pasting)
510         {
511             if (parseSequence(pasteStart))
512             {
513                 pasting = true;
514                 pasteBuf = "";
515                 pasteTime = now;
516                 return true;
517             }
518             return false;
519         }
520         assert(pasting);
521 
522         // we end the sequence if we see it, but also if we timed out looking for it
523         if (parseSequence(pasteEnd) ||
524             ((now - pasteTime) > seqTime * 4))
525         {
526             pasting = false;
527             auto ev = newPasteEvent(pasteBuf);
528             pasteBuf = "";
529             evs ~= ev;
530             return true;
531         }
532         if (parseRune())
533         {
534             pasteTime = now;
535         }
536         // we pretend to have eaten the entire thing, even if there is stuff left
537         return true;
538     }
539 
540     bool parseRune()
541     {
542         if (buf.length == 0)
543         {
544             return false;
545         }
546         dchar dc;
547         Modifiers mod = Modifiers.none;
548         if ((buf[0] >= ' ' && buf[0] < 0x7F) ||
549             (pasting && isWhite(buf[0])))
550         {
551             dc = buf[0];
552             buf = buf[1 .. $];
553             // printable ascii, easy to deal with
554             if (escaped)
555             {
556                 escaped = false;
557                 mod = Modifiers.alt;
558             }
559             if (pasting)
560                 pasteBuf ~= dc;
561             else
562                 evs ~= newKeyEvent(Key.rune, dc, mod);
563             return true;
564         }
565         if ((buf[0] < 0x80) || (buf[0] == 0x7F)) // control character, not a rune
566             return false;
567         // unicode bits...
568         size_t index = 0;
569         auto temp = cast(string) buf;
570         try
571         {
572             dc = temp.decode(index);
573         }
574         catch (UTFException e)
575         {
576             return false;
577         }
578         if (pasting)
579             pasteBuf ~= dc;
580         else
581             evs ~= newKeyEvent(Key.rune, dc, mod);
582         buf = buf[index .. $];
583         return true;
584     }
585 
586     bool parseFnKey()
587     {
588         Modifiers mod = Modifiers.none;
589         if (buf.length == 0)
590         {
591             return false;
592         }
593         foreach (seq, kc; keyCodes)
594         {
595             // ideally we'd use a trie to isolate
596             // duplicates for this.  for now, we just handle esc.
597             if (seq.length == 1 && seq[0] == '\x1b')
598             {
599                 continue;
600             }
601             if (parseSequence(seq))
602             {
603                 mod = kc.mod;
604                 if (escaped)
605                 {
606                     escaped = false;
607                     mod = Modifiers.alt;
608                 }
609                 evs ~= newKeyEvent(kc.key, seq.length == 1 ? seq[0] : 0, mod);
610                 return true;
611             }
612         }
613         return false;
614     }
615 
616     // look for an SGR mouse record, using a state machine
617     bool parseSgrMouse()
618     {
619         int x, y, btn, mov, state, val;
620         bool dig, neg;
621 
622         for (int i = 0; i < buf.length; i++)
623         {
624             switch (buf[i])
625             {
626             case '\x1b':
627                 if (state != 0)
628                     return false;
629                 state = 1;
630                 break;
631             case '\x9b': // 8-bit mode CSI
632                 if (state != 0)
633                     return false;
634                 state = 2;
635                 break;
636             case '[':
637                 if (state != 1)
638                     return false;
639                 state = 2;
640                 break;
641             case '<':
642                 if (state != 2)
643                     return false;
644                 val = 0;
645                 dig = false;
646                 neg = false;
647                 state = 3;
648                 break;
649             case '-':
650                 if (state < 3 || state > 5 || dig || neg)
651                     return false;
652                 neg = true; // no state change
653                 break;
654             case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
655                 if (state < 3 || state > 5)
656                     return false;
657                 val *= 10;
658                 val += int(buf[i] - '0');
659                 dig = true; // no state change
660                 break;
661             case ';':
662                 if (state != 3 && state != 4)
663                     return false;
664                 val = neg ? -val : val;
665                 if (state == 3)
666                     btn = neg ? -val : val;
667                 else
668                     x = (neg ? -val : val) - 1;
669                 state++;
670                 neg = false;
671                 dig = false;
672                 val = 0;
673                 break;
674             case 'm', 'M':
675                 if (state != 5)
676                     return false;
677                 y = (neg ? -val : val) - 1;
678                 mov = (btn & 0x20) != 0;
679                 btn ^= 0x20;
680                 if (buf[i] == 'm')
681                 { // release
682                     btn |= 0x3;
683                     btn ^= 0x40;
684                     buttonDown = false;
685                 }
686                 else if (mov)
687                 {
688                     // Some broken terminals appear to send
689                     // mouse button one motion events, instead of
690                     // encoding 35 (no buttons) into these events.
691                     // We resolve these by looking for a non-motion
692                     // event first.
693                     if (!buttonDown)
694                     {
695                         btn |= 0x3;
696                         btn ^= 0x40;
697                     }
698                 }
699                 else
700                 {
701                     buttonDown = true;
702                 }
703                 buf = buf[i + 1 .. $];
704                 import std.stdio;
705 
706                 evs ~= newMouseEvent(x, y, btn);
707                 return true;
708             default:
709                 // definitely not ours
710                 return false;
711             }
712         }
713         // might be ours, inconclusive
714         if (state > 0)
715             partial = true;
716         return false;
717     }
718 
719     bool parseLegacyMouse()
720     {
721         int x, y, btn;
722 
723         enum State
724         {
725             start,
726             bracket,
727             end
728         }
729 
730         State state;
731 
732         for (int i = 0; i < buf.length; i++)
733         {
734             final switch (state)
735             {
736             case State.start:
737                 switch (buf[i])
738                 {
739                 case '\x1b':
740                     state = State.bracket;
741                     break;
742                 case '\x9b':
743                     state = State.end;
744                     break;
745                 default:
746                     return false;
747                 }
748                 break;
749             case State.bracket:
750                 if (buf[i] != '[')
751                     return false;
752                 state++;
753                 break;
754             case State.end:
755                 if (buf[i] != 'M')
756                     return false;
757                 if (buf.length < i + 4)
758                     break;
759                 buf = buf[i + 1 .. $];
760                 btn = int(buf[0]);
761                 x = int(buf[1]) - 32 - 1;
762                 y = int(buf[2]) - 32 - 1;
763                 buf = buf[3 .. $];
764                 evs ~= newMouseEvent(x, y, btn);
765                 return true;
766             }
767         }
768         if (state > 0)
769             partial = true;
770         return false;
771     }
772 }
773 
774 unittest
775 {
776     import core.thread;
777     import dcell.database;
778 
779     // taken from xterm, but pared down
780     static immutable Termcap term = {
781         name: "test-term",
782         enterKeypad: "\x1b[?1h\x1b=",
783         exitKeypad: "\x1b[?1l\x1b>",
784         cursorBack1: "\x08",
785         cursorUp1: "\x1b[A",
786         keyBackspace: "\x08",
787         keyF1: "\x1bOP",
788         keyF2: "\x1bOQ",
789         keyF3: "\x1bOR",
790         keyInsert: "\x1b[2~",
791         keyDelete: "\x1b[3~",
792         keyHome: "\x1bOH",
793         keyEnd: "\x1bOF",
794         keyPgUp: "\x1b[5~",
795         keyPgDn: "\x1b[6~",
796         keyUp: "\x1bOA",
797         keyDown: "\x1bOB",
798         keyLeft: "\x1bOD",
799         keyRight: "\x1bOC",
800         keyBacktab: "\x1b[Z",
801         keyShfRight: "\x1b[1;2C",
802         mouse: "\x1b[M",
803     };
804     Database.put(&term);
805     auto tc = Database.get("test-term");
806     assert(tc !is null);
807     Parser p = new Parser(ParseKeys(tc));
808     assert(p.empty());
809     assert(p.parse("")); // no data, is fine
810     assert(p.parse("\x1bOC"));
811     auto ev = p.events();
812 
813     assert(ev.length == 1);
814     assert(ev[0].type == EventType.key);
815     assert(ev[0].key.key == Key.right);
816 
817     // this tests that the timed pase parsing works -
818     // escape sequences are kept partially until we
819     // have a match or we have waited long enough.
820     assert(p.parse(['\x1b', 'O']) == false);
821     ev = p.events();
822     assert(ev.length == 0);
823     Thread.sleep(msecs(100));
824     assert(p.parse([]) == true);
825     ev = p.events();
826     assert(ev.length == 1);
827     assert(ev[0].type == EventType.key);
828     assert(ev[0].key.key == Key.rune);
829     assert(ev[0].key.mod == Modifiers.alt);
830 
831     // lone escape
832     assert(p.parse(['\x1b']) == false);
833     ev = p.events();
834     assert(ev.length == 0);
835     Thread.sleep(msecs(100));
836     assert(p.parse([]) == true);
837     ev = p.events();
838     assert(ev.length == 1);
839     assert(ev[0].type == EventType.key);
840     assert(ev[0].key.key == Key.esc);
841     assert(ev[0].key.mod == Modifiers.none);
842 
843     // try injecting paste events
844     assert(tc.enablePaste != "");
845     assert(p.parse(['\x1b', '[', '2', '0', '0', '~']));
846     assert(p.parse(['A']));
847     assert(p.parse(['\x1b', '[', '2', '0', '1', '~']));
848 
849     ev = p.events();
850     assert(ev.length == 1);
851     assert(ev[0].type == EventType.paste);
852     assert(ev[0].paste.content == "A");
853 
854     // mouse events
855     assert(p.parse(['\x1b', '[', '<', '3', ';', '2', ';', '3', 'M']));
856     ev = p.events();
857     assert(ev.length == 1);
858     assert(ev[0].type == EventType.mouse);
859     assert(ev[0].mouse.pos.x == 1);
860     assert(ev[0].mouse.pos.y == 2);
861 
862     // unicode
863     string b = [0xe2, 0x82, 0xac];
864     assert(p.parse(b));
865     ev = p.events();
866     assert(ev.length == 1);
867     assert(ev[0].type == EventType.key);
868     assert(ev[0].key.key == Key.rune);
869     assert(ev[0].key.ch == '€');
870 }