(U-00024532): Use `->denotational' and `->subsumptive'.
[chise/xemacs-chise.git-] / src / termcap.c
1 /* Work-alike for termcap, plus extra features.
2    Copyright (C) 1985, 1986, 1993 Free Software Foundation, Inc.
3
4 This file is part of XEmacs.
5
6 XEmacs is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
9 later version.
10
11 XEmacs is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with XEmacs; see the file COPYING.  If not, write to
18 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA.  */
20
21 /* Synched up with: Not synched with FSF. */
22
23 /* config.h may rename various library functions such as malloc.  */
24 #ifdef emacs
25 #include <config.h>
26 #include "lisp.h" /* For encapsulated open, close, read */
27 #include "device.h" /* For DEVICE_BAUD_RATE */
28 #else /* not emacs */
29
30 #include <stdlib.h>
31 #include <string.h>
32
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #ifdef _POSIX_VERSION
37 #include <fcntl.h>
38 #endif
39
40 #endif /* not emacs */
41
42 /* BUFSIZE is the initial size allocated for the buffer
43    for reading the termcap file.
44    It is not a limit.
45    Make it large normally for speed.
46    Make it variable when debugging, so can exercise
47    increasing the space dynamically.  */
48
49 #ifndef BUFSIZE
50 #ifdef DEBUG
51 #define BUFSIZE bufsize
52
53 int bufsize = 128;
54 #else
55 #define BUFSIZE 2048
56 #endif
57 #endif
58
59 #ifndef emacs
60 static void
61 memory_out ()
62 {
63   write (2, "virtual memory exhausted\n", 25);
64   exit (1);
65 }
66
67 static char *
68 xmalloc (size)
69      unsigned int size;
70 {
71   char *tem = malloc (size);
72
73   if (!tem)
74     memory_out ();
75   return tem;
76 }
77
78 static char *
79 xrealloc (ptr, size)
80      char *ptr;
81      unsigned size;
82 {
83   char *tem = realloc (ptr, size);
84
85   if (!tem)
86     memory_out ();
87   return tem;
88 }
89 #endif /* not emacs */
90 \f
91 /* Looking up capabilities in the entry already found.  */
92
93 /* The pointer to the data made by tgetent is left here
94    for tgetnum, tgetflag and tgetstr to find.  */
95 static char *term_entry;
96
97 static const char *tgetst1 (const char *ptr, char **area);
98
99 /* Search entry BP for capability CAP.
100    Return a pointer to the capability (in BP) if found,
101    0 if not found.  */
102
103 static const char *
104 find_capability (bp, cap)
105      const char *bp;
106      const char *cap;
107 {
108   for (; *bp; bp++)
109     if (bp[0] == ':'
110         && bp[1] == cap[0]
111         && bp[2] == cap[1])
112       return &bp[4];
113   return 0;
114 }
115
116 int
117 tgetnum (cap)
118      const char *cap;
119 {
120   const char *ptr = find_capability (term_entry, cap);
121   if (!ptr || ptr[-1] != '#')
122     return -1;
123   return atoi (ptr);
124 }
125
126 int
127 tgetflag (cap)
128      const char *cap;
129 {
130   const char *ptr = find_capability (term_entry, cap);
131   return 0 != ptr && ptr[-1] == ':';
132 }
133
134 /* Look up a string-valued capability CAP.
135    If AREA is nonzero, it points to a pointer to a block in which
136    to store the string.  That pointer is advanced over the space used.
137    If AREA is zero, space is allocated with `malloc'.  */
138
139 const char *
140 tgetstr (cap, area)
141      const char *cap;
142      char **area;
143 {
144   const char *ptr = find_capability (term_entry, cap);
145   if (!ptr || (ptr[-1] != '=' && ptr[-1] != '~'))
146     return 0;
147   return tgetst1 (ptr, area);
148 }
149
150 /* Table, indexed by a character in range 0100 to 0140 with 0100 subtracted,
151    gives meaning of character following \, or a space if no special meaning.
152    Eight characters per line within the string.  */
153
154 static char esctab[]
155   = " \007\010  \033\014 "
156 "      \012 "
157 "  \015 \011 \013 "
158 "        ";
159
160 /* PTR points to a string value inside a termcap entry.
161    Copy that value, processing \ and ^ abbreviations,
162    into the block that *AREA points to,
163    or to newly allocated storage if AREA is 0.  */
164
165 static const char *
166 tgetst1 (ptr, area)
167      const char *ptr;
168      char **area;
169 {
170   const char *p;
171   char *r;
172   int c;
173   int size;
174   char *ret;
175   int c1;
176
177   if (!ptr)
178     return 0;
179
180   /* `ret' gets address of where to store the string.  */
181   if (!area)
182     {
183       /* Compute size of block needed (may overestimate).  */
184       p = ptr;
185       while ((c = *p++) && c != ':' && c != '\n')
186         ;
187       ret = (char *) xmalloc (p - ptr + 1);
188     }
189   else
190     ret = *area;
191
192   /* Copy the string value, stopping at null or colon.
193      Also process ^ and \ abbreviations.  */
194   p = ptr;
195   r = ret;
196   while ((c = *p++) && c != ':' && c != '\n')
197     {
198       if (c == '^')
199         c = *p++ & 037;
200       else if (c == '\\')
201         {
202           c = *p++;
203           if (c >= '0' && c <= '7')
204             {
205               c -= '0';
206               size = 0;
207
208               while (++size < 3 && (c1 = *p) >= '0' && c1 <= '7')
209                 {
210                   c *= 8;
211                   c += c1 - '0';
212                   p++;
213                 }
214             }
215           else if (c >= 0100 && c < 0200)
216             {
217               c1 = esctab[(c & ~040) - 0100];
218               if (c1 != ' ')
219                 c = c1;
220             }
221         }
222       *r++ = c;
223     }
224   *r = 0;
225   /* Update *AREA.  */
226   if (area)
227     *area = r + 1;
228   return ret;
229 }
230 \f
231 /* Outputting a string with padding.  */
232
233 #ifdef LINUX
234 speed_t ospeed;
235 #else
236 short ospeed;
237 #endif
238 /* If `ospeed' is 0, we use `tputs_baud_rate' as the actual baud rate.  */
239 int tputs_baud_rate;
240 char PC;
241
242 /* Actual baud rate if positive;
243    - baud rate / 100 if negative.  */
244
245 static short speeds[] =
246   {
247     0, 50, 75, 110, 135, 150, -2, -3, -6, -12,
248     -18, -24, -48, -96, -192, -288, -384, -576, -1152
249   };
250
251 void
252 tputs (string, nlines, outfun)
253      const char *string;
254      int nlines;
255      void (*outfun) (int);
256 {
257   int padcount = 0;
258   int speed;
259
260 #ifdef emacs
261   speed = DEVICE_BAUD_RATE (XDEVICE (Fselected_device (Qnil)));
262 #else
263   if (ospeed == 0)
264     speed = tputs_baud_rate;
265   else
266     speed = speeds[ospeed];
267 #endif
268
269   if (string == (char *) 0)
270     return;
271
272   while (isdigit (* (const unsigned char *) string))
273     {
274       padcount += *string++ - '0';
275       padcount *= 10;
276     }
277   if (*string == '.')
278     {
279       string++;
280       padcount += *string++ - '0';
281     }
282   if (*string == '*')
283     {
284       string++;
285       padcount *= nlines;
286     }
287   while (*string)
288     (*outfun) (*string++);
289
290   /* padcount is now in units of tenths of msec.  */
291   padcount *= speeds[ospeed];
292   padcount += 500;
293   padcount /= 1000;
294   if (speeds[ospeed] < 0)
295     padcount = -padcount;
296   else
297     {
298       padcount += 50;
299       padcount /= 100;
300     }
301
302   while (padcount-- > 0)
303     (*outfun) (PC);
304 }
305 \f
306 /* Finding the termcap entry in the termcap data base.  */
307
308 struct buffer
309   {
310     char *beg;
311     int size;
312     char *ptr;
313     int ateof;
314     int full;
315   };
316
317 /* Forward declarations of static functions.  */
318
319 static int scan_file ();
320 static char *gobble_line ();
321 static int compare_contin ();
322 static int name_match ();
323
324
325 /* Find the termcap entry data for terminal type NAME
326    and store it in the block that BP points to.
327    Record its address for future use.
328
329    If BP is zero, space is dynamically allocated.  */
330
331 int
332 tgetent (bp, name)
333      char *bp;
334      const char *name;
335 {
336   char *tem;
337   int fd;
338   struct buffer buf;
339   char *bp1;
340   char *bp2;
341   const char *term;
342   int malloc_size = 0;
343   int c;
344   char *tcenv;                  /* TERMCAP value, if it contains :tc=.  */
345   const char *indirect = 0;     /* Terminal type in :tc= in TERMCAP value.  */
346
347   tem = getenv ("TERMCAP");
348   if (tem && *tem == 0) tem = 0;
349
350
351   /* If tem is non-null and starts with / (in the un*x case, that is),
352      it is a file name to use instead of /etc/termcap.
353      If it is non-null and does not start with /,
354      it is the entry itself, but only if
355      the name the caller requested matches the TERM variable.  */
356
357   if (tem && !IS_DIRECTORY_SEP (*tem) && !strcmp (name, getenv ("TERM")))
358     {
359       indirect = tgetst1 (find_capability (tem, "tc"), 0);
360       if (!indirect)
361         {
362           if (!bp)
363             bp = tem;
364           else
365             strcpy (bp, tem);
366           goto ret;
367         }
368       else
369         {                       /* We will need to read /etc/termcap.  */
370           tcenv = tem;
371           tem = 0;
372         }
373     }
374   else
375     indirect = (char *) 0;
376
377   if (!tem)
378     tem = "/etc/termcap";
379
380   /* Here we know we must search a file and tem has its name.  */
381
382   fd = open (tem, 0, 0);
383   if (fd < 0)
384     return -1;
385
386   buf.size = BUFSIZE;
387   /* Add 1 to size to ensure room for terminating null.  */
388   buf.beg = (char *) xmalloc (buf.size + 1);
389   term = indirect ? indirect : name;
390
391   if (!bp)
392     {
393       malloc_size = indirect ? strlen (tcenv) + 1 : buf.size;
394       bp = (char *) xmalloc (malloc_size);
395     }
396   bp1 = bp;
397
398   if (indirect)
399     /* Copy the data from the environment variable.  */
400     {
401       strcpy (bp, tcenv);
402       bp1 += strlen (tcenv);
403     }
404
405   while (term)
406     {
407       /* Scan the file, reading it via buf, till find start of main entry.  */
408       if (scan_file (term, fd, &buf) == 0)
409         return 0;
410
411       /* Free old `term' if appropriate.  */
412       if (term != name)
413         xfree (term);
414
415       /* If BP is malloc'd by us, make sure it is big enough.  */
416       if (malloc_size)
417         {
418           malloc_size = bp1 - bp + buf.size;
419           tem = (char *) xrealloc (bp, malloc_size);
420           bp1 += tem - bp;
421           bp = tem;
422         }
423
424       bp2 = bp1;
425
426       /* Copy the line of the entry from buf into bp.  */
427       tem = buf.ptr;
428       while ((*bp1++ = c = *tem++) && c != '\n')
429         /* Drop out any \ newline sequence.  */
430         if (c == '\\' && *tem == '\n')
431           {
432             bp1--;
433             tem++;
434           }
435       *bp1 = 0;
436
437       /* Does this entry refer to another terminal type's entry?
438          If something is found, copy it into heap and null-terminate it.  */
439       term = tgetst1 (find_capability (bp2, "tc"), 0);
440     }
441
442   close (fd);
443   xfree (buf.beg);
444
445   if (malloc_size)
446     {
447       bp = (char *) xrealloc (bp, bp1 - bp + 1);
448     }
449
450  ret:
451   term_entry = bp;
452   if (malloc_size)
453     /* #### yuck, why the hell are we casting a pointer to an int? */
454     return (int) (long) bp;
455   return 1;
456 }
457
458 /* Given file open on FD and buffer BUFP,
459    scan the file from the beginning until a line is found
460    that starts the entry for terminal type STRING.
461    Returns 1 if successful, with that line in BUFP,
462    or returns 0 if no entry found in the file.  */
463
464 static int
465 scan_file (string, fd, bufp)
466      char *string;
467      int fd;
468      struct buffer *bufp;
469 {
470   char *end;
471
472   bufp->ptr = bufp->beg;
473   bufp->full = 0;
474   bufp->ateof = 0;
475   *bufp->ptr = 0;
476
477   lseek (fd, 0L, 0);
478
479   while (!bufp->ateof)
480     {
481       /* Read a line into the buffer.  */
482       end = 0;
483       do
484         {
485           /* if it is continued, append another line to it,
486              until a non-continued line ends.  */
487           end = gobble_line (fd, bufp, end);
488         }
489       while (!bufp->ateof && end[-2] == '\\');
490
491       if (*bufp->ptr != '#'
492           && name_match (bufp->ptr, string))
493         return 1;
494
495       /* Discard the line just processed.  */
496       bufp->ptr = end;
497     }
498   return 0;
499 }
500
501 /* Return nonzero if NAME is one of the names specified
502    by termcap entry LINE.  */
503
504 static int
505 name_match (line, name)
506      char *line, *name;
507 {
508   char *tem;
509
510   if (!compare_contin (line, name))
511     return 1;
512   /* This line starts an entry.  Is it the right one?  */
513   for (tem = line; *tem && *tem != '\n' && *tem != ':'; tem++)
514     if (*tem == '|' && !compare_contin (tem + 1, name))
515       return 1;
516
517   return 0;
518 }
519
520 static int
521 compare_contin (str1, str2)
522      char *str1, *str2;
523 {
524   int c1, c2;
525   while (1)
526     {
527       c1 = *str1++;
528       c2 = *str2++;
529       while (c1 == '\\' && *str1 == '\n')
530         {
531           str1++;
532           while ((c1 = *str1++) == ' ' || c1 == '\t');
533         }
534       if (c2 == '\0')
535         {
536           /* End of type being looked up.  */
537           if (c1 == '|' || c1 == ':')
538             /* If end of name in data base, we win.  */
539             return 0;
540           else
541             return 1;
542         }
543       else if (c1 != c2)
544         return 1;
545     }
546 }
547
548 /* Make sure that the buffer <- BUFP contains a full line
549    of the file open on FD, starting at the place BUFP->ptr
550    points to.  Can read more of the file, discard stuff before
551    BUFP->ptr, or make the buffer bigger.
552
553    Returns the pointer to after the newline ending the line,
554    or to the end of the file, if there is no newline to end it.
555
556    Can also merge on continuation lines.  If APPEND_END is
557    nonzero, it points past the newline of a line that is
558    continued; we add another line onto it and regard the whole
559    thing as one line.  The caller decides when a line is continued.  */
560
561 static char *
562 gobble_line (fd, bufp, append_end)
563      int fd;
564      struct buffer *bufp;
565      char *append_end;
566 {
567   char *end;
568   int nread;
569   char *buf = bufp->beg;
570   char *tem;
571
572   if (append_end == 0)
573     append_end = bufp->ptr;
574
575   while (1)
576     {
577       end = append_end;
578       while (*end && *end != '\n') end++;
579       if (*end)
580         break;
581       if (bufp->ateof)
582         return buf + bufp->full;
583       if (bufp->ptr == buf)
584         {
585           if (bufp->full == bufp->size)
586             {
587               bufp->size *= 2;
588               /* Add 1 to size to ensure room for terminating null.  */
589               tem = (char *) xrealloc (buf, bufp->size + 1);
590               bufp->ptr = (bufp->ptr - buf) + tem;
591               append_end = (append_end - buf) + tem;
592               bufp->beg = buf = tem;
593             }
594         }
595       else
596         {
597           append_end -= bufp->ptr - buf;
598           memcpy (buf, bufp->ptr, bufp->full -= bufp->ptr - buf);
599           bufp->ptr = buf;
600         }
601       if (!(nread = read (fd, buf + bufp->full, bufp->size - bufp->full)))
602         bufp->ateof = 1;
603       bufp->full += nread;
604       buf[bufp->full] = 0;
605     }
606   return end + 1;
607 }
608 \f
609 #ifdef TEST
610
611 #include <stdio.h>
612
613 main (argc, argv)
614      int argc;
615      char **argv;
616 {
617   char *term;
618   char *buf;
619
620   term = argv[1];
621   printf ("TERM: %s\n", term);
622
623   buf = (char *) tgetent (0, term);
624   if ((int) buf <= 0)
625     {
626       printf ("No entry.\n");
627       return 0;
628     }
629
630   printf ("Entry: %s\n", buf);
631
632   tprint ("cm");
633   tprint ("AL");
634
635   printf ("co: %d\n", tgetnum ("co"));
636   printf ("am: %d\n", tgetflag ("am"));
637 }
638
639 tprint (cap)
640      const char *cap;
641 {
642   char *x = tgetstr (cap, 0);
643   char *y;
644
645   printf ("%s: ", cap);
646   if (x)
647     {
648       for (y = x; *y; y++)
649         if (*y <= ' ' || *y == 0177)
650           printf ("\\%0o", *y);
651         else
652           putchar (*y);
653       xfree (x);
654     }
655   else
656     printf ("none");
657   putchar ('\n');
658 }
659
660 #endif /* TEST */
661