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