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