xref: /vim-8.2.3635/src/termlib.c (revision 2bf24176)
1 /* vi:set ts=8 sts=4 sw=4: */
2 /*
3  * The following software is (C) 1984 Peter da Silva, the Mad Australian, in
4  * the public domain. It may be re-distributed for any purpose with the
5  * inclusion of this notice.
6  */
7 
8 /* Modified by Bram Moolenaar for use with VIM - Vi Improved. */
9 /* A few bugs removed by Olaf 'Rhialto' Seibert. */
10 
11 /* TERMLIB: Terminal independent database. */
12 
13 #include "vim.h"
14 #include "termlib.pro"
15 
16 #if !defined(AMIGA) && !defined(VMS) && !defined(MACOS)
17 # include <sgtty.h>
18 #endif
19 
20 static int  getent __ARGS((char *, char *, FILE *, int));
21 static int  nextent __ARGS((char *, FILE *, int));
22 static int  _match __ARGS((char *, char *));
23 static char *_addfmt __ARGS((char *, char *, int));
24 static char *_find __ARGS((char *, char *));
25 
26 /*
27  * Global variables for termlib
28  */
29 
30 char	*tent;		      /* Pointer to terminal entry, set by tgetent */
31 char	PC = 0;		      /* Pad character, default NULL */
32 char	*UP = 0, *BC = 0;     /* Pointers to UP and BC strings from database */
33 short	ospeed;		      /* Baud rate (1-16, 1=300, 16=19200), as in stty */
34 
35 /*
36  * Module: tgetent
37  *
38  * Purpose: Get termcap entry for <term> into buffer at <tbuf>.
39  *
40  * Calling conventions: char tbuf[TBUFSZ+], term=canonical name for terminal.
41  *
42  * Returned values: 1 = success, -1 = can't open file,
43  *	    0 = can't find terminal.
44  *
45  * Notes:
46  * - Should probably supply static buffer.
47  * - Uses environment variables "TERM" and "TERMCAP". If TERM = term (that is,
48  *   if the argument matches the environment) then it looks at TERMCAP.
49  * - If TERMCAP begins with a slash, then it assumes this is the file to
50  *   search rather than /etc/termcap.
51  * - If TERMCAP does not begin with a slash, and it matches TERM, then this is
52  *   used as the entry.
53  * - This could be simplified considerably for non-UNIX systems.
54  */
55 
56 #ifndef TERMCAPFILE
57 # ifdef AMIGA
58 #  define TERMCAPFILE "s:termcap"
59 # else
60 #  ifdef VMS
61 #   define TERMCAPFILE "VIMRUNTIME:termcap"
62 #  else
63 #   define TERMCAPFILE "/etc/termcap"
64 #  endif
65 # endif
66 #endif
67 
68     int
69 tgetent(tbuf, term)
70     char    *tbuf;		/* Buffer to hold termcap entry, TBUFSZ bytes max */
71     char    *term;		/* Name of terminal */
72 {
73     char    tcbuf[32];		/* Temp buffer to handle */
74     char    *tcptr = tcbuf;	/* extended entries */
75     char    *tcap = TERMCAPFILE; /* Default termcap file */
76     char    *tmp;
77     FILE    *termcap;
78     int	    retval = 0;
79     int	    len;
80 
81     if ((tmp = (char *)mch_getenv((char_u *)"TERMCAP")) != NULL)
82     {
83 	if (*tmp == '/')		/* TERMCAP = name of termcap file */
84 	{
85 	    tcap = tmp ;
86 #if defined(AMIGA)
87 	    /* Convert /usr/share/lib/termcap to usr:share/lib/termcap */
88 	    tcap++;
89 	    tmp = strchr(tcap, '/');
90 	    if (tmp)
91 		*tmp = ':';
92 #endif
93 	}
94 	else				/* TERMCAP = termcap entry itself */
95 	{
96 	    int tlen = strlen(term);
97 
98 	    while (*tmp && *tmp != ':')		/* Check if TERM matches */
99 	    {
100 		char *nexttmp;
101 
102 		while (*tmp == '|')
103 		    tmp++;
104 		nexttmp  = _find(tmp, ":|");	/* Rhialto */
105 		if (tmp+tlen == nexttmp && _match(tmp, term) == tlen)
106 		{
107 		    strcpy(tbuf, tmp);
108 		    tent = tbuf;
109 		    return 1;
110 		}
111 		else
112 		    tmp = nexttmp;
113 	    }
114 	}
115     }
116     if (!(termcap = mch_fopen(tcap, "r")))
117     {
118 	strcpy(tbuf, tcap);
119 	return -1;
120     }
121 
122     len = 0;
123     while (getent(tbuf + len, term, termcap, TBUFSZ - len))
124     {
125 	tcptr = tcbuf;				/* Rhialto */
126 	if ((term = tgetstr("tc", &tcptr)))	/* extended entry */
127 	{
128 	    rewind(termcap);
129 	    len = strlen(tbuf);
130 	}
131 	else
132 	{
133 	    retval = 1;
134 	    tent = tbuf;	/* reset it back to the beginning */
135 	    break;
136 	}
137     }
138     fclose(termcap);
139     return retval;
140 }
141 
142     static int
143 getent(tbuf, term, termcap, buflen)
144     char    *tbuf, *term;
145     FILE    *termcap;
146     int	    buflen;
147 {
148     char    *tptr;
149     int	    tlen = strlen(term);
150 
151     while (nextent(tbuf, termcap, buflen))	/* For each possible entry */
152     {
153 	tptr = tbuf;
154 	while (*tptr && *tptr != ':')		/* : terminates name field */
155 	{
156 	    char    *nexttptr;
157 
158 	    while (*tptr == '|')		/* | separates names */
159 		tptr++;
160 	    nexttptr = _find(tptr, ":|");	/* Rhialto */
161 	    if (tptr + tlen == nexttptr &&
162 		_match(tptr, term) == tlen)	/* FOUND! */
163 	    {
164 		tent = tbuf;
165 		return 1;
166 	    }
167 	    else				/* Look for next name */
168 		tptr = nexttptr;
169 	}
170     }
171     return 0;
172 }
173 
174     static int
175 nextent(tbuf, termcap, buflen)		/* Read 1 entry from TERMCAP file */
176     char    *tbuf;
177     FILE    *termcap;
178     int	    buflen;
179 {
180     char *lbuf = tbuf;				/* lbuf=line buffer */
181 				/* read lines straight into buffer */
182 
183     while (lbuf < tbuf+buflen &&		/* There's room and */
184 	  fgets(lbuf, (int)(tbuf+buflen-lbuf), termcap)) /* another line */
185     {
186 	int llen = strlen(lbuf);
187 
188 	if (*lbuf == '#')			/* eat comments */
189 	    continue;
190 	if (lbuf[-1] == ':' &&			/* and whitespace */
191 	    lbuf[0] == '\t' &&
192 	    lbuf[1] == ':')
193 	{
194 	    STRMOVE(lbuf, lbuf + 2);
195 	    llen -= 2;
196 	}
197 	if (lbuf[llen-2] == '\\')		/* and continuations */
198 	    lbuf += llen-2;
199 	else
200 	{
201 	    lbuf[llen-1]=0;			/* no continuation, return */
202 	    return 1;
203 	}
204     }
205 
206     return 0;					/* ran into end of file */
207 }
208 
209 /*
210  * Module: tgetflag
211  *
212  * Purpose: returns flag true or false as to the existence of a given entry.
213  * used with 'bs', 'am', etc...
214  *
215  * Calling conventions: id is the 2 character capability id.
216  *
217  * Returned values: 1 for success, 0 for failure.
218  */
219 
220     int
221 tgetflag(id)
222     char *id;
223 {
224     char    buf[256], *ptr = buf;
225 
226     return tgetstr(id, &ptr) ? 1 : 0;
227 }
228 
229 /*
230  * Module: tgetnum
231  *
232  * Purpose: get numeric value such as 'li' or 'co' from termcap.
233  *
234  * Calling conventions: id = 2 character id.
235  *
236  * Returned values: -1 for failure, else numerical value.
237  */
238 
239     int
240 tgetnum(id)
241     char *id;
242 {
243     char *ptr, buf[256];
244     ptr = buf;
245 
246     if (tgetstr(id, &ptr))
247 	return atoi(buf);
248     else
249 	return 0;
250 }
251 
252 /*
253  * Module: tgetstr
254  *
255  * Purpose: get terminal capability string from database.
256  *
257  * Calling conventions: id is the two character capability id.
258  *	    (*buf) points into a hold buffer for the
259  *	    id. the capability is copied into the buffer
260  *	    and (*buf) is advanced to point to the next
261  *	    free byte in the buffer.
262  *
263  * Returned values: 0 = no such entry, otherwise returns original
264  *	    (*buf) (now a pointer to the string).
265  *
266  * Notes
267  *	It also decodes certain escape sequences in the buffer.
268  *  they should be obvious from the code:
269  *	\E = escape.
270  *	\n, \r, \t, \f, \b match the 'c' escapes.
271  *	^x matches control-x (^@...^_).
272  *	\nnn matches nnn octal.
273  *	\x, where x is anything else, matches x. I differ
274  *  from the standard library here, in that I allow ^: to match
275  *  :.
276  *
277  */
278 
279     char *
280 tgetstr(id, buf)
281     char	*id, **buf;
282 {
283     int		len = strlen(id);
284     char	*tmp=tent;
285     char	*hold;
286     int		i;
287 
288     do {
289 	tmp = _find(tmp, ":");			/* For each field */
290 	while (*tmp == ':')			/* skip empty fields */
291 	    tmp++;
292 	if (!*tmp)
293 	    break;
294 
295 	if (_match(id, tmp) == len) {
296 	    tmp += len;				/* find '=' '@' or '#' */
297 	    if (*tmp == '@')			/* :xx@: entry for tc */
298 		return 0;			/* deleted entry */
299 	    hold= *buf;
300 	    while (*++tmp && *tmp != ':') {	/* not at end of field */
301 		switch(*tmp) {
302 		case '\\':			/* Expand escapes here */
303 		    switch(*++tmp) {
304 		    case 0:			/* ignore backslashes */
305 			tmp--;			/* at end of entry */
306 			break;			/* shouldn't happen */
307 		    case 'e':
308 		    case 'E':			/* ESC */
309 			*(*buf)++ = ESC;
310 			break;
311 		    case 'n':			/* \n */
312 			*(*buf)++ = '\n';
313 			break;
314 		    case 'r':			/* \r */
315 			*(*buf)++ = '\r';
316 			break;
317 		    case 't':			/* \t */
318 			*(*buf)++ = '\t';
319 			break;
320 		    case 'b':			/* \b */
321 			*(*buf)++ = '\b';
322 			break;
323 		    case 'f':			/* \f */
324 			*(*buf)++ = '\f';
325 			break;
326 		    case '0':			/* \nnn */
327 		    case '1':
328 		    case '2':
329 		    case '3':
330 		    case '4':
331 		    case '5':
332 		    case '6':
333 		    case '7':
334 		    case '8':
335 		    case '9':
336 			**buf = 0;
337 			    /* get up to three digits */
338 			for (i = 0; i < 3 && VIM_ISDIGIT(*tmp); ++i)
339 			    **buf = **buf * 8 + *tmp++ - '0';
340 			(*buf)++;
341 			tmp--;
342 			break;
343 		    default:			/* \x, for all other x */
344 			*(*buf)++= *tmp;
345 		    }
346 		    break;
347 		case '^':			/* control characters */
348 		    ++tmp;
349 		    *(*buf)++ = Ctrl_chr(*tmp);
350 		    break;
351 		default:
352 		    *(*buf)++ = *tmp;
353 		}
354 	    }
355 	    *(*buf)++ = 0;
356 	    return hold;
357 	}
358     } while (*tmp);
359 
360     return 0;
361 }
362 
363 /*
364  * Module: tgoto
365  *
366  * Purpose: decode cm cursor motion string.
367  *
368  * Calling conventions: cm is cursor motion string.  line, col, are the
369  * desired destination.
370  *
371  * Returned values: a string pointing to the decoded string, or "OOPS" if it
372  * cannot be decoded.
373  *
374  * Notes
375  *	The accepted escapes are:
376  *	%d	 as in printf, 0 origin.
377  *	%2, %3   like %02d, %03d in printf.
378  *	%.	 like %c
379  *	%+x	 adds <x> to value, then %.
380  *	%>xy     if value>x, adds y. No output.
381  *	%i	 increments line& col, no output.
382  *	%r	 reverses order of line&col. No output.
383  *	%%	 prints as a single %.
384  *	%n	 exclusive or row & col with 0140.
385  *	%B	 BCD, no output.
386  *	%D	 reverse coding (x-2*(x%16)), no output.
387  */
388 
389     char *
390 tgoto(cm, col, line)
391     char	*cm;				/* cm string, from termcap */
392     int col,					/* column, x position */
393     line;					/* line, y position */
394 {
395     char    gx, gy,				/* x, y */
396 	*ptr,					/* pointer in 'cm' */
397 	reverse = 0,				/* reverse flag */
398 	*bufp,					/* pointer in returned string */
399 	addup = 0,				/* add upline */
400 	addbak = 0,				/* add backup */
401 	c;
402     static char buffer[32];
403 
404     if (!cm)
405 	return "OOPS";				/* Kludge, but standard */
406 
407     bufp = buffer;
408     ptr = cm;
409 
410     while (*ptr) {
411 	if ((c = *ptr++) != '%') {		/* normal char */
412 	    *bufp++ = c;
413 	} else {				/* % escape */
414 	    switch(c = *ptr++) {
415 	    case 'd':				/* decimal */
416 		bufp = _addfmt(bufp, "%d", line);
417 		line = col;
418 		break;
419 	    case '2':				/* 2 digit decimal */
420 		bufp = _addfmt(bufp, "%02d", line);
421 		line = col;
422 		break;
423 	    case '3':				/* 3 digit decimal */
424 		bufp = _addfmt(bufp, "%03d", line);
425 		line = col;
426 		break;
427 	    case '>':				/* %>xy: if >x, add y */
428 		gx = *ptr++;
429 		gy = *ptr++;
430 		if (col>gx) col += gy;
431 		if (line>gx) line += gy;
432 		break;
433 	    case '+':				/* %+c: add c */
434 		line += *ptr++;
435 	    case '.':				/* print x/y */
436 		if (line == '\t' ||		/* these are */
437 		   line == '\n' ||		/* chars that */
438 		   line == '\004' ||		/* UNIX hates */
439 		   line == '\0') {
440 		    line++;			/* so go to next pos */
441 		    if (reverse == (line == col))
442 			addup=1;		/* and mark UP */
443 		    else
444 			addbak=1;		/* or BC */
445 		}
446 		*bufp++=line;
447 		line = col;
448 		break;
449 	    case 'r':				/* r: reverse */
450 		gx = line;
451 		line = col;
452 		col = gx;
453 		reverse = 1;
454 		break;
455 	    case 'i':			/* increment (1-origin screen) */
456 		col++;
457 		line++;
458 		break;
459 	    case '%':				/* %%=% literally */
460 		*bufp++='%';
461 		break;
462 	    case 'n':				/* magic DM2500 code */
463 		line ^= 0140;
464 		col ^= 0140;
465 		break;
466 	    case 'B':				/* bcd encoding */
467 		line = line/10<<4+line%10;
468 		col = col/10<<4+col%10;
469 		break;
470 	    case 'D':				/* magic Delta Data code */
471 		line = line-2*(line&15);
472 		col = col-2*(col&15);
473 		break;
474 	    default:				/* Unknown escape */
475 		return "OOPS";
476 	    }
477 	}
478     }
479 
480     if (addup)					/* add upline */
481 	if (UP) {
482 	    ptr=UP;
483 	    while (VIM_ISDIGIT(*ptr) || *ptr == '.')
484 		ptr++;
485 	    if (*ptr == '*')
486 		ptr++;
487 	    while (*ptr)
488 		*bufp++ = *ptr++;
489 	}
490 
491     if (addbak)					/* add backspace */
492 	if (BC) {
493 	    ptr=BC;
494 	    while (VIM_ISDIGIT(*ptr) || *ptr == '.')
495 		ptr++;
496 	    if (*ptr == '*')
497 		ptr++;
498 	    while (*ptr)
499 		*bufp++ = *ptr++;
500 	}
501 	else
502 	    *bufp++='\b';
503 
504     *bufp = 0;
505 
506     return(buffer);
507 }
508 
509 /*
510  * Module: tputs
511  *
512  * Purpose: decode padding information
513  *
514  * Calling conventions: cp = string to be padded, affcnt = # of items affected
515  *	(lines, characters, whatever), outc = routine to output 1 character.
516  *
517  * Returned values: none
518  *
519  * Notes
520  *	cp has padding information ahead of it, in the form
521  *  nnnTEXT or nnn*TEXT. nnn is the number of milliseconds to delay,
522  *  and may be a decimal (nnn.mmm). If the asterisk is given, then
523  *  the delay is multiplied by afcnt. The delay is produced by outputting
524  *  a number of nulls (or other padding char) after printing the
525  *  TEXT.
526  *
527  */
528 
529 long _bauds[16]={
530     0,	50, 75,	110,
531     134,    150,    200,    300,
532     600,    1200,   1800,   2400,
533     4800,   9600,   19200,  19200 };
534 
535     int
536 tputs(cp, affcnt, outc)
537     char *cp;				/* string to print */
538     int affcnt;				/* Number of lines affected */
539     void (*outc) __ARGS((unsigned int));/* routine to output 1 character */
540 {
541     long    frac,			/* 10^(#digits after decimal point) */
542 	counter,			/* digits */
543 	atol __ARGS((const char *));
544 
545     if (VIM_ISDIGIT(*cp)) {
546 	counter = 0;
547 	frac = 1000;
548 	while (VIM_ISDIGIT(*cp))
549 	    counter = counter * 10L + (long)(*cp++ - '0');
550 	if (*cp == '.')
551 	    while (VIM_ISDIGIT(*++cp)) {
552 		counter = counter * 10L + (long)(*cp++ - '0');
553 		frac = frac * 10;
554 	    }
555 	if (*cp!='*') {			/* multiply by affected lines */
556 	    if (affcnt>1) affcnt = 1;
557 	}
558 	else
559 	    cp++;
560 
561 	/* Calculate number of characters for padding counter/frac ms delay */
562 	if (ospeed)
563 	    counter = (counter * _bauds[ospeed] * (long)affcnt) / frac;
564 
565 	while (*cp)			/* output string */
566 	    (*outc)(*cp++);
567 	if (ospeed)
568 	    while (counter--)		/* followed by pad characters */
569 		(*outc)(PC);
570     }
571     else
572 	while (*cp)
573 	    (*outc)(*cp++);
574     return 0;
575 }
576 
577 /*
578  * Module: tutil.c
579  *
580  * Purpose: Utility routines for TERMLIB functions.
581  *
582  */
583     static int
584 _match(s1, s2)		/* returns length of text common to s1 and s2 */
585     char *s1, *s2;
586 {
587     int i = 0;
588 
589     while (s1[i] && s1[i] == s2[i])
590 	i++;
591 
592     return i;
593 }
594 
595 /*
596  * finds next c in s that's a member of set, returns pointer
597  */
598     static char *
599 _find(s, set)
600     char *s, *set;
601 {
602     for(; *s; s++)
603     {
604 	char	*ptr = set;
605 
606 	while (*ptr && *s != *ptr)
607 	    ptr++;
608 
609 	if (*ptr)
610 	    return s;
611     }
612 
613     return s;
614 }
615 
616 /*
617  * add val to buf according to format fmt
618  */
619     static char *
620 _addfmt(buf, fmt, val)
621     char *buf, *fmt;
622     int val;
623 {
624     sprintf(buf, fmt, val);
625     while (*buf)
626 	buf++;
627     return buf;
628 }
629