chiark / gitweb /
256f7fb95d4d508d458e68d3d3f8631ea33d381b
[sympathy.git] / src / ansi.c
1 /*
2  * ansi.c:
3  *
4  * Copyright (c) 2008 James McKenzie <james@fishsoup.dhs.org>,
5  * All rights reserved.
6  *
7  */
8
9 static char rcsid[] = "$Id$";
10
11 /*
12  * $Log$
13  * Revision 1.29  2008/02/23 11:48:37  james
14  * *** empty log message ***
15  *
16  * Revision 1.28  2008/02/22 17:07:00  james
17  * *** empty log message ***
18  *
19  * Revision 1.27  2008/02/20 22:54:22  staffcvs
20  * *** empty log message ***
21  *
22  * Revision 1.26  2008/02/20 20:16:07  james
23  * *** empty log message ***
24  *
25  * Revision 1.25  2008/02/20 19:44:37  james
26  * @@
27  *
28  * Revision 1.24  2008/02/20 19:36:06  james
29  * @@
30  *
31  * Revision 1.23  2008/02/20 19:25:09  james
32  * *** empty log message ***
33  *
34  * Revision 1.22  2008/02/15 03:32:07  james
35  * *** empty log message ***
36  *
37  * Revision 1.21  2008/02/14 02:46:44  james
38  * *** empty log message ***
39  *
40  * Revision 1.20  2008/02/14 01:55:57  james
41  * *** empty log message ***
42  *
43  * Revision 1.19  2008/02/13 16:57:29  james
44  * *** empty log message ***
45  *
46  * Revision 1.18  2008/02/13 09:12:21  james
47  * *** empty log message ***
48  *
49  * Revision 1.17  2008/02/13 01:08:18  james
50  * *** empty log message ***
51  *
52  * Revision 1.16  2008/02/07 13:22:51  james
53  * *** empty log message ***
54  *
55  * Revision 1.15  2008/02/07 13:19:48  james
56  * *** empty log message ***
57  *
58  * Revision 1.14  2008/02/07 12:21:16  james
59  * *** empty log message ***
60  *
61  * Revision 1.13  2008/02/07 12:16:04  james
62  * *** empty log message ***
63  *
64  * Revision 1.12  2008/02/07 11:32:41  james
65  * *** empty log message ***
66  *
67  * Revision 1.11  2008/02/07 11:11:14  staffcvs
68  * *** empty log message ***
69  *
70  * Revision 1.10  2008/02/07 01:02:52  james
71  * *** empty log message ***
72  *
73  * Revision 1.9  2008/02/07 00:43:27  james
74  * *** empty log message ***
75  *
76  * Revision 1.8  2008/02/07 00:39:13  james
77  * *** empty log message ***
78  *
79  * Revision 1.7  2008/02/06 20:26:57  james
80  * *** empty log message ***
81  *
82  * Revision 1.6  2008/02/06 17:53:28  james
83  * *** empty log message ***
84  *
85  * Revision 1.5  2008/02/06 15:53:22  james
86  * *** empty log message ***
87  *
88  * Revision 1.4  2008/02/04 20:23:55  james
89  * *** empty log message ***
90  *
91  * Revision 1.3  2008/02/04 05:45:55  james
92  * ::
93  *
94  * Revision 1.2  2008/02/04 02:05:06  james
95  * *** empty log message ***
96  *
97  * Revision 1.1  2008/02/03 23:31:25  james
98  * *** empty log message ***
99  *
100  */
101 #include "project.h"
102
103
104 static void
105 ansi_move (ANSI * a, CRT_Pos p)
106 {
107   char buf[16];
108   int n;
109   int dx = p.x - a->pos.x;
110   int dy = p.y - a->pos.y;
111
112 //  a->pos.x = ANSI_INVAL;
113
114   if (a->pos.x != ANSI_INVAL)
115     {
116
117       if ((!dx) && (!dy))
118         return;
119
120       if (!dy)
121         {
122           if (dx == 1)
123             {
124               a->terminal->xmit (a->terminal, "\033[C", 3);
125             }
126           else if (dx == -1)
127             {
128               a->terminal->xmit (a->terminal, "\033[D", 3);
129             }
130           else
131             {
132               n = snprintf (buf, sizeof (buf), "\033[%dG", p.x + 1);
133               a->terminal->xmit (a->terminal, buf, n);
134             }
135         }
136       else if (!dx)
137         {
138           if (dy == -1)
139             {
140               a->terminal->xmit (a->terminal, "\033[A", 3);
141             }
142           else if (dy == 1)
143             {
144               a->terminal->xmit (a->terminal, "\033[B", 3);
145             }
146           else if (dy < 0)
147             {
148               n = snprintf (buf, sizeof (buf), "\033[%dA", -dy);
149               a->terminal->xmit (a->terminal, buf, n);
150             }
151           else
152             {
153               n = snprintf (buf, sizeof (buf), "\033[%dB", dy);
154               a->terminal->xmit (a->terminal, buf, n);
155             }
156         }
157       else if (!p.x)
158         {
159           if (dy == 1)
160             {
161               a->terminal->xmit (a->terminal, "\033[E", 3);
162             }
163           else if (dy == -1)
164             {
165               a->terminal->xmit (a->terminal, "\033[F", 3);
166             }
167           else if (dy > 0)
168             {
169               n = snprintf (buf, sizeof (buf), "\033[%dE", dy);
170               a->terminal->xmit (a->terminal, buf, n);
171             }
172           else
173             {
174               n = snprintf (buf, sizeof (buf), "\033[%dF", -dy);
175               a->terminal->xmit (a->terminal, buf, n);
176             }
177         }
178       else
179         {
180           n = snprintf (buf, sizeof (buf), "\033[%d;%dH", p.y + 1, p.x + 1);
181           a->terminal->xmit (a->terminal, buf, n);
182         }
183     }
184   else
185     {
186       n = snprintf (buf, sizeof (buf), "\033[%d;%dH", p.y + 1, p.x + 1);
187       a->terminal->xmit (a->terminal, buf, n);
188     }
189
190   a->pos = p;
191 }
192
193
194 static void
195 ansi_showhide_cursor (ANSI * a, int hide)
196 {
197   if (a->hide_cursor == hide)
198     return;
199
200   if (hide)
201     {
202       a->terminal->xmit (a->terminal, "\033[?25l", 6);
203     }
204   else
205     {
206       a->terminal->xmit (a->terminal, "\033[?25h", 6);
207     }
208
209   a->hide_cursor = hide;
210 }
211
212
213 static void
214 ansi_force_attr_normal (ANSI * a)
215 {
216   a->terminal->xmit (a->terminal, "\033[0m", 4);
217   a->attr = CRT_ATTR_NORMAL;
218   a->color = ANSI_INVAL;
219 }
220
221 static void
222 ansi_set_color (ANSI * a, int color)
223 {
224   int dif;
225   char buf[16];
226   int i;
227   int fg, bg;
228
229   if ((a->color == ANSI_INVAL) || (color != a->color))
230     {
231       fg = CRT_COLOR_FG (color);
232       bg = CRT_COLOR_BG (color);
233
234       if (fg & CRT_COLOR_INTENSITY)
235         {
236           fg += 90;
237         }
238       else
239         {
240           fg += 30;
241         }
242
243       if (bg & CRT_COLOR_INTENSITY)
244         {
245           bg += 100;
246         }
247       else
248         {
249           bg += 40;
250         }
251
252       i = sprintf (buf, "\033[%d;%dm", fg, bg);
253 #if 0
254       fprintf (stderr, "Color set to %d %d %x\n", fg, bg, color);
255 #endif
256
257       a->terminal->xmit (a->terminal, buf, i);
258       a->color = color;
259     }
260 }
261
262 static void
263 ansi_set_attr (ANSI * a, int attr)
264 {
265   int dif;
266
267   dif = attr ^ a->attr;
268
269   if (!dif)
270     return;
271
272   a->attr = attr;
273
274 #if 0
275   if (attr == CRT_ATTR_NORMAL)
276     {
277       ansi_force_attr_normal (a);
278       return;
279     }
280 #endif
281
282   if (dif & CRT_ATTR_UNDERLINE)
283     {
284       if (attr & CRT_ATTR_UNDERLINE)
285         {
286           a->terminal->xmit (a->terminal, "\033[4m", 4);
287         }
288       else
289         {
290           a->terminal->xmit (a->terminal, "\033[24m", 5);
291         }
292     }
293   if (dif & CRT_ATTR_REVERSE)
294     {
295       if (attr & CRT_ATTR_REVERSE)
296         {
297           a->terminal->xmit (a->terminal, "\033[7m", 4);
298         }
299       else
300         {
301           a->terminal->xmit (a->terminal, "\033[27m", 5);
302         }
303     }
304   if (dif & CRT_ATTR_BOLD)
305     {
306       if (attr & CRT_ATTR_BOLD)
307         {
308           a->terminal->xmit (a->terminal, "\033[1m", 4);
309         }
310       else
311         {
312           a->terminal->xmit (a->terminal, "\033[21m", 5);
313           a->terminal->xmit (a->terminal, "\033[22m", 5);
314         }
315     }
316
317 }
318
319
320 static void
321 ansi_render (ANSI * a, CRT_CA ca)
322 {
323   int dif;
324
325   if (ca.chr < 32)
326     ca.chr = ' ';
327   if (ca.chr > 126)
328     ca.chr = ' ';
329
330   ansi_set_attr (a, ca.attr);
331   ansi_set_color (a, ca.color);
332
333   a->terminal->xmit (a->terminal, &ca.chr, 1);
334
335   a->pos.x++;
336
337 /*Can't easily wrap round here as don't know size of destination screen*/
338 /*so invalidate the cached cursor position*/
339
340   if (a->pos.x >= a->size.x)
341     a->pos.x = ANSI_INVAL;
342
343 }
344
345 static void
346 ansi_cls (ANSI * a)
347 {
348   CRT_Pos p = { 0 };
349
350   crt_cls (&a->crt);
351
352   ansi_set_attr (a, CRT_ATTR_NORMAL);
353   ansi_set_color (a, CRT_COLOR_NORMAL);
354   ansi_move (a, p);
355   a->terminal->xmit (a->terminal, "\033[2J", 4);
356 /*different emulators leave cursor in different places after cls differently*/
357   a->pos.x = ANSI_INVAL;
358 }
359
360 #if 0
361 int
362 ansi_scroll_up (ANSI * a, CRT_Pos s, CRT_Pos e)
363 {
364   char buf[16];
365   int i;
366   if (s.x)
367     return -1;
368   if (e.x != (CRT_COLS - 1))
369     return -1;
370   if (s.y)
371     return -1;
372   if (s.y >= a->size.y)
373     return -1;
374
375   ansi_showhide_cursor (a, 1);
376   i = sprintf (buf, "\033[%d;%dr", s.y + 1, e.y + 1);
377   a->terminal->xmit (a->terminal, buf, i);
378   i = sprintf (buf, "\033[%d;%dH", e.y + 1, 0);
379   a->terminal->xmit (a->terminal, buf, i);
380   a->terminal->xmit (a->terminal, "\033D", 2);
381   a->terminal->xmit (a->terminal, "\033[r", 3);
382
383   s.y = e.y;
384   crt_erase (&a->crt, s, e, 1);
385
386   a->pos.x = ANSI_INVAL;
387
388   return 0;
389 }
390
391
392 static void
393 ansi_spot_scroll_up (ANSI * a, CRT * c)
394 {
395   int l, n, p;
396
397   l = c->sh.e.x - c->sh.s.x;
398   l++;
399   l *= sizeof (CRT_CA);
400
401   n = c->sh.e.y - c->sh.s.y;
402   p = CRT_ADDR_POS (&c->sh.s);
403
404   while (n--)
405     {
406       if (memcmp (&c->screen[p], &a->crt.screen[p + CRT_COLS], l))
407         return;
408       p += CRT_COLS;
409     }
410
411   if (ansi_scroll_up (a, c->sh.s, c->sh.e))
412     return;
413
414   n = c->sh.e.y - c->sh.s.y;
415   p = CRT_ADDR_POS (&c->sh.s);
416
417   while (n--)
418     {
419       memcpy (&a->crt.screen[p], &a->crt.screen[p + CRT_COLS], l);
420       p += CRT_COLS;
421     }
422
423   c->sh.dir = 0;                //FIXME: horrid hack
424
425 }
426
427 static void
428 ansi_spot_scroll (ANSI * a, CRT * c)
429 {
430
431   switch (c->sh.dir)
432     {
433     case -1:
434 /*we only care about up for the moment */
435       ansi_spot_scroll_up (a, c);
436       break;
437     }
438
439   return;
440 }
441
442 #endif
443
444
445 static void
446 ansi_draw_line (ANSI * a, CRT_CA * cap, int y)
447 {
448   CRT_Pos p = { 0, y };
449   CRT_CA *acap = &a->crt.screen[CRT_ADDR_POS (&p)];
450
451   for (p.x = 0; p.x < CRT_COLS; ++p.x)
452     {
453       if (p.x >= a->size.x)
454         continue;
455
456       if (crt_ca_cmp (*acap, *cap))
457         {
458           ansi_showhide_cursor (a, 1);
459           *acap = *cap;
460
461           ansi_move (a, p);
462           ansi_render (a, *acap);
463         }
464
465       acap++;
466       cap++;
467     }
468 }
469
470 static void
471 ansi_resize_check (ANSI * a)
472 {
473
474   if (!crt_pos_cmp (a->terminal->size, a->size))
475     return;
476
477   terminal_getsize (a->terminal);
478
479   a->size = a->terminal->size;
480
481   a->pos.x = ANSI_INVAL;
482   a->hide_cursor = ANSI_INVAL;
483
484   crt_reset (&a->crt);
485
486 // FIXME: -- echos back crap?
487 //  a->terminal->xmit (a->terminal, "\033[c", 3);
488
489   ansi_cls (a);
490   a->terminal->xmit (a->terminal, "\033=", 2);
491   a->terminal->xmit (a->terminal, "\033[?6l", 5);
492   a->terminal->xmit (a->terminal, "\033[r", 3);
493
494 }
495
496 #define HISTORY_GUESS_SCROLL 24 /*guess all 24 lines usually scroll */
497 /*if they haven't then ansi_draw will patch it up*/
498
499 static void
500 ansi_history (ANSI * a, History * h)
501 {
502   char buf[32];
503   int i;
504 /*Do we need to catch up on history?*/
505
506   if (a->history_ptr == h->wptr)
507     return;
508   ansi_resize_check (a);
509
510   if ((a->size.x < CRT_COLS) || (a->size.y < CRT_ROWS))
511     return;
512
513   ansi_force_attr_normal (a);
514   ansi_set_color (a, CRT_COLOR_NORMAL);
515
516   i = sprintf (buf, "\033[%d;%dr", 1, HISTORY_GUESS_SCROLL);
517   a->terminal->xmit (a->terminal, buf, i);
518
519
520   while (a->history_ptr != h->wptr)
521     {
522
523       History_ent *e = &h->lines[a->history_ptr];
524
525       HISTORY_INC (h, a->history_ptr);
526
527       if (!e->valid)
528         continue;
529
530       /*If so write the line ot the top of the screen */
531       ansi_draw_line (a, e->line, 0);
532
533
534       /*Roll HISTORY_GUESS_SCROLL lines up putting the top line into the xterm's history */
535
536
537       /*Make extra lines a predictable colour */
538       ansi_set_color (a, CRT_COLOR_NORMAL);
539
540       ansi_showhide_cursor (a, 1);
541       i = sprintf (buf, "\033[%d;%dH", HISTORY_GUESS_SCROLL, 1);
542       a->terminal->xmit (a->terminal, buf, i);
543       a->terminal->xmit (a->terminal, "\033D", 2);
544       a->pos.x = ANSI_INVAL;
545
546       /*now do the same in our image of the screen */
547
548       {
549         CRT_Pos s = { 0 }
550         , e =
551         {
552         0};
553
554         /*scroll lines up */
555         for (s.y++; s.y < HISTORY_GUESS_SCROLL; s.y++, e.y++)
556           {
557             memcpy (&a->crt.screen[CRT_ADDR_POS (&e)],
558                     &a->crt.screen[CRT_ADDR_POS (&s)],
559                     sizeof (CRT_CA) * CRT_COLS);
560           }
561
562         /* erase new line */
563         s.y = e.y;
564         e.x = CRT_COLS - 1;
565         crt_erase (&a->crt, s, e, 1, CRT_COLOR_NORMAL);
566       }
567
568     }
569 /*reset margins*/
570   a->terminal->xmit (a->terminal, "\033[r", 3);
571   a->pos.x = ANSI_INVAL;
572
573 }
574
575
576
577
578 static void
579 ansi_draw (ANSI * a, CRT * c)
580 {
581   CRT_Pos p;
582   int o;
583   int hidden_cursor = 0;
584
585   ansi_resize_check (a);
586
587
588
589   for (p.y = 0; p.y < CRT_ROWS; ++p.y)
590     {
591       if (p.y >= a->size.y)
592         continue;
593
594       ansi_draw_line (a, &c->screen[CRT_ADDR (p.y, 0)], p.y);
595
596     }
597
598
599   if ((CRT_COLS > a->size.x) || (CRT_ROWS > a->size.y))
600     {
601       char msg[1024];           // = "Window is too small";
602       int i;
603       p.x = 0;
604       p.y = 0;
605
606       i =
607         sprintf (msg, "Window too small (%dx%d need %dx%d)", a->size.x,
608                  a->size.y, CRT_COLS, CRT_ROWS);
609
610       ansi_showhide_cursor (a, 1);
611       ansi_set_attr (a, CRT_ATTR_REVERSE);
612       ansi_set_color (a, CRT_MAKE_COLOR (CRT_COLOR_WHITE, CRT_COLOR_RED));
613       ansi_move (a, p);
614
615       a->terminal->xmit (a->terminal, msg, i);
616       a->pos.x = ANSI_INVAL;
617     }
618
619
620   if ((c->pos.x >= a->size.x) || (c->pos.y >= a->size.y))
621     {
622       ansi_showhide_cursor (a, 1);
623       return;
624     }
625
626   a->crt.pos = c->pos;
627   ansi_move (a, a->crt.pos);
628
629   a->crt.hide_cursor = c->hide_cursor;
630   ansi_showhide_cursor (a, a->crt.hide_cursor);
631 }
632
633 static void
634 ansi_reset (ANSI * a, CRT * c)
635 {
636   a->size.x = -1;
637   ansi_draw (a, c ? c : &a->crt);
638 }
639
640 static void
641 ansi_terminal_reset (ANSI * a)
642 {
643   CRT_Pos p = { 0, CRT_ROWS };
644   ansi_force_attr_normal (a);
645
646   ansi_move (a, p);
647 }
648
649 static void
650 ansi_flush_escape (ANSI * a, Context * c)
651 {
652   ANSI_Parser *p = &a->parser;
653   int i;
654
655   for (i = 0; i < p->escape_ptr; ++i)
656     {
657       keydis_key (c->k, c, p->escape_buf[i]);
658     }
659
660   p->escape_ptr = 0;
661   p->in_escape = 0;
662 }
663
664 static void
665 ansi_parse_deckey (ANSI * a, Context * c)
666 {
667   ANSI_Parser *p = &a->parser;
668   if ((p->escape_buf[1] != '[') && (p->escape_buf[1] != 'O'))
669     {
670       ansi_flush_escape (a, c);
671       return;
672     }
673
674   if ((p->escape_buf[2] >= 'A') || (p->escape_buf[2] <= 'Z'))
675     {
676       keydis_key (c->k, c, KEY_UP + (p->escape_buf[2] - 'A'));
677     }
678   else if ((p->escape_buf[2] >= 'a') || (p->escape_buf[2] <= 'z'))
679     {
680       keydis_key (c->k, c, KEY_154 + (p->escape_buf[2] - 'a'));
681     }
682   else
683     {
684       ansi_flush_escape (a, c);
685       return;
686     }
687   p->in_escape = 0;
688   p->escape_ptr = 0;
689 }
690
691 static void
692 ansi_parse_ansikey (ANSI * a, Context * c)
693 {
694   ANSI_Parser *p = &a->parser;
695
696   if ((p->escape_buf[1] != '[') || (p->escape_buf[3] != '~'))
697     {
698       ansi_flush_escape (a, c);
699       return;
700     }
701   if ((p->escape_buf[2] >= '0') || (p->escape_buf[2] <= '9'))
702     {
703       keydis_key (c->k, c, KEY_180 + (p->escape_buf[2] - '0'));
704     }
705   else
706     {
707       ansi_flush_escape (a, c);
708       return;
709     }
710
711   p->in_escape = 0;
712   p->escape_ptr = 0;
713 }
714
715
716
717 static void
718 ansi_parse_escape (ANSI * a, Context * c)
719 {
720   ANSI_Parser *p = &a->parser;
721   switch (p->escape_ptr)
722     {
723     case 0:
724     case 1:
725       return;
726     case 2:
727       switch (p->escape_buf[1])
728         {
729         case '[':
730         case 'O':
731           break;
732         default:
733           ansi_flush_escape (a, c);
734         }
735       break;
736     case 3:
737       switch (p->escape_buf[1])
738         {
739         case 'O':
740           ansi_parse_deckey (a, c);
741           break;
742         case '[':
743           if ((p->escape_buf[2] >= 'A') && (p->escape_buf[2] <= 'Z'))
744             ansi_parse_deckey (a, c);
745           break;
746         default:
747           ansi_flush_escape (a, c);
748         }
749       break;
750     case 4:
751       switch (p->escape_buf[1])
752         {
753         case '[':
754           ansi_parse_ansikey (a, c);
755           break;
756         default:
757           ansi_flush_escape (a, c);
758         }
759       break;
760     case 5:
761       ansi_flush_escape (a, c);
762     }
763 }
764
765
766 static void
767 ansi_check_escape (ANSI * a, Context * c)
768 {
769   ANSI_Parser *p = &a->parser;
770   struct timeval now, diff;
771   gettimeofday (&now, NULL);
772   timersub (&now, &p->last_escape, &diff);
773
774 #if 0
775   fprintf (stderr, "ie %d tl %d.%06d eb %d\n",
776            p->in_escape, diff.tv_sec, diff.tv_usec, p->escape_ptr);
777 #endif
778
779   if (!p->in_escape)
780     return;
781
782
783   /*Time up? */
784   if (diff.tv_sec || (diff.tv_usec > ANSI_ESCAPE_TIMEOUT))
785     ansi_flush_escape (a, c);
786
787 }
788
789
790 static void
791 ansi_parse_char (ANSI * a, Context * c, int ch)
792 {
793   ANSI_Parser *p = &a->parser;
794
795
796 /*See if it's time to flush the escape*/
797   ansi_check_escape (a, c);
798
799   if (ch == 033)
800     {
801       if (p->in_escape)
802         ansi_flush_escape (a, c);
803
804       p->in_escape++;
805       p->escape_ptr = 0;
806       gettimeofday (&p->last_escape, NULL);
807     }
808
809   if (p->in_escape)
810     {
811       p->escape_buf[p->escape_ptr++] = ch;
812       ansi_parse_escape (a, c);
813     }
814   else
815     {
816       keydis_key (c->k, c, ch);
817     }
818
819 }
820
821 static void
822 ansi_parse (ANSI * a, Context * c, char *buf, int len)
823 {
824   while (len--)
825     ansi_parse_char (a, c, *(buf++));
826 }
827
828 int
829 ansi_dispatch (ANSI * a, Context * c)
830 {
831   char buf[1024];
832   int red;
833
834   ansi_check_escape (a, c);
835
836
837   if (!a->terminal)
838     return 0;
839
840   red = a->terminal->recv (a->terminal, buf, sizeof (buf));
841   if (red <= 0)
842     return red;
843
844 #if 0
845   if (*buf == 3)
846     return -1;
847 #endif
848
849 #if 0
850   if (*buf == 2)
851     {
852 #if  0
853       a->history_ptr = c->h->wptr;
854       HISTORY_INC (c->h, a->history_ptr);
855 #endif
856       return -1;
857     }
858 #endif
859
860
861   ansi_parse (a, c, buf, red);
862
863   return 0;
864 }
865
866
867 static void
868 ansi_update (ANSI * a, Context * c)
869 {
870   ansi_history (a, c->h);
871   ansi_draw (a, &c->v->crt);
872 }
873
874 static void
875 ansi_free (ANSI * a)
876 {
877   a->terminal_reset (a);
878   if (a->terminal)
879     a->terminal->close (a->terminal);
880
881   free (a);
882
883 }
884
885 ANSI *
886 ansi_new_from_terminal (TTY * t)
887 {
888   ANSI *ret;
889
890   ret = malloc (sizeof (ANSI));
891   memset (ret, 0, sizeof (ANSI));
892
893   ret->terminal = t;
894
895   ret->update = ansi_update;
896   ret->reset = ansi_reset;
897   ret->terminal_reset = ansi_terminal_reset;
898   ret->close = ansi_free;
899   ret->dispatch = ansi_dispatch;
900
901   return ret;
902 }