1 /* vi:set ts=8 sts=4 sw=4 noet: */
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)
17 # include <sgtty.h>
18 #endif
19 
20 static int  getent(char *, char *, FILE *, int);
21 static int  nextent(char *, FILE *, int);
22 static int  _match(char *, char *);
23 static char *_addfmt(char *, char *, int);
24 static char *_find(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
tgetent(char * tbuf,char * term)69 tgetent(
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
getent(char * tbuf,char * term,FILE * termcap,int buflen)143 getent(char *tbuf, char *term, FILE *termcap, int buflen)
144 {
145     char    *tptr;
146     int	    tlen = strlen(term);
147 
148     while (nextent(tbuf, termcap, buflen))	// For each possible entry
149     {
150 	tptr = tbuf;
151 	while (*tptr && *tptr != ':')		// : terminates name field
152 	{
153 	    char    *nexttptr;
154 
155 	    while (*tptr == '|')		// | separates names
156 		tptr++;
157 	    nexttptr = _find(tptr, ":|");	// Rhialto
158 	    if (tptr + tlen == nexttptr &&
159 		_match(tptr, term) == tlen)	// FOUND!
160 	    {
161 		tent = tbuf;
162 		return 1;
163 	    }
164 	    else				// Look for next name
165 		tptr = nexttptr;
166 	}
167     }
168     return 0;
169 }
170 
171 /*
172  * Read 1 entry from TERMCAP file.
173  */
174     static int
nextent(char * tbuf,FILE * termcap,int buflen)175 nextent(char *tbuf, FILE *termcap, int buflen)
176 {
177     char *lbuf = tbuf;				// lbuf=line buffer
178 				// read lines straight into buffer
179 
180     while (lbuf < tbuf+buflen &&		// There's room and
181 	  fgets(lbuf, (int)(tbuf+buflen-lbuf), termcap)) // another line
182     {
183 	int llen = strlen(lbuf);
184 
185 	if (*lbuf == '#')			// eat comments
186 	    continue;
187 	if (lbuf[-1] == ':' &&			// and whitespace
188 	    lbuf[0] == '\t' &&
189 	    lbuf[1] == ':')
190 	{
191 	    STRMOVE(lbuf, lbuf + 2);
192 	    llen -= 2;
193 	}
194 	if (lbuf[llen-2] == '\\')		// and continuations
195 	    lbuf += llen-2;
196 	else
197 	{
198 	    lbuf[llen-1]=0;			// no continuation, return
199 	    return 1;
200 	}
201     }
202 
203     return 0;					// ran into end of file
204 }
205 
206 /*
207  * Module: tgetflag
208  *
209  * Purpose: returns flag true or false as to the existence of a given entry.
210  * used with 'bs', 'am', etc...
211  *
212  * Calling conventions: id is the 2 character capability id.
213  *
214  * Returned values: 1 for success, 0 for failure.
215  */
216 
217     int
tgetflag(char * id)218 tgetflag(char *id)
219 {
220     char    buf[256], *ptr = buf;
221 
222     return tgetstr(id, &ptr) ? 1 : 0;
223 }
224 
225 /*
226  * Module: tgetnum
227  *
228  * Purpose: get numeric value such as 'li' or 'co' from termcap.
229  *
230  * Calling conventions: id = 2 character id.
231  *
232  * Returned values: -1 for failure, else numerical value.
233  */
234 
235     int
tgetnum(char * id)236 tgetnum(char *id)
237 {
238     char *ptr, buf[256];
239     ptr = buf;
240 
241     if (tgetstr(id, &ptr))
242 	return atoi(buf);
243     else
244 	return 0;
245 }
246 
247 /*
248  * Module: tgetstr
249  *
250  * Purpose: get terminal capability string from database.
251  *
252  * Calling conventions: id is the two character capability id.
253  *	    (*buf) points into a hold buffer for the
254  *	    id. the capability is copied into the buffer
255  *	    and (*buf) is advanced to point to the next
256  *	    free byte in the buffer.
257  *
258  * Returned values: 0 = no such entry, otherwise returns original
259  *	    (*buf) (now a pointer to the string).
260  *
261  * Notes
262  *	It also decodes certain escape sequences in the buffer.
263  *  they should be obvious from the code:
264  *	\E = escape.
265  *	\n, \r, \t, \f, \b match the 'c' escapes.
266  *	^x matches control-x (^@...^_).
267  *	\nnn matches nnn octal.
268  *	\x, where x is anything else, matches x. I differ
269  *  from the standard library here, in that I allow ^: to match
270  *  :.
271  *
272  */
273 
274     char *
tgetstr(char * id,char ** buf)275 tgetstr(char *id, char **buf)
276 {
277     int		len = strlen(id);
278     char	*tmp=tent;
279     char	*hold;
280     int		i;
281 
282     do {
283 	tmp = _find(tmp, ":");			// For each field
284 	while (*tmp == ':')			// skip empty fields
285 	    tmp++;
286 	if (!*tmp)
287 	    break;
288 
289 	if (_match(id, tmp) == len) {
290 	    tmp += len;				// find '=' '@' or '#'
291 	    if (*tmp == '@')			// :xx@: entry for tc
292 		return 0;			// deleted entry
293 	    hold= *buf;
294 	    while (*++tmp && *tmp != ':') {	// not at end of field
295 		switch(*tmp) {
296 		case '\\':			// Expand escapes here
297 		    switch(*++tmp) {
298 		    case 0:			// ignore backslashes
299 			tmp--;			// at end of entry
300 			break;			// shouldn't happen
301 		    case 'e':
302 		    case 'E':			// ESC
303 			*(*buf)++ = ESC;
304 			break;
305 		    case 'n':			// \n
306 			*(*buf)++ = '\n';
307 			break;
308 		    case 'r':			// \r
309 			*(*buf)++ = '\r';
310 			break;
311 		    case 't':			// \t
312 			*(*buf)++ = '\t';
313 			break;
314 		    case 'b':			// \b
315 			*(*buf)++ = '\b';
316 			break;
317 		    case 'f':			// \f
318 			*(*buf)++ = '\f';
319 			break;
320 		    case '0':			// \nnn
321 		    case '1':
322 		    case '2':
323 		    case '3':
324 		    case '4':
325 		    case '5':
326 		    case '6':
327 		    case '7':
328 		    case '8':
329 		    case '9':
330 			**buf = 0;
331 			    // get up to three digits
332 			for (i = 0; i < 3 && VIM_ISDIGIT(*tmp); ++i)
333 			    **buf = **buf * 8 + *tmp++ - '0';
334 			(*buf)++;
335 			tmp--;
336 			break;
337 		    default:			// \x, for all other x
338 			*(*buf)++= *tmp;
339 		    }
340 		    break;
341 		case '^':			// control characters
342 		    ++tmp;
343 		    *(*buf)++ = Ctrl_chr(*tmp);
344 		    break;
345 		default:
346 		    *(*buf)++ = *tmp;
347 		}
348 	    }
349 	    *(*buf)++ = 0;
350 	    return hold;
351 	}
352     } while (*tmp);
353 
354     return 0;
355 }
356 
357 /*
358  * Module: tgoto
359  *
360  * Purpose: decode cm cursor motion string.
361  *
362  * Calling conventions: cm is cursor motion string.  line, col, are the
363  * desired destination.
364  *
365  * Returned values: a string pointing to the decoded string, or "OOPS" if it
366  * cannot be decoded.
367  *
368  * Notes
369  *	The accepted escapes are:
370  *	%d	 as in printf, 0 origin.
371  *	%2, %3   like %02d, %03d in printf.
372  *	%.	 like %c
373  *	%+x	 adds <x> to value, then %.
374  *	%>xy     if value>x, adds y. No output.
375  *	%i	 increments line& col, no output.
376  *	%r	 reverses order of line&col. No output.
377  *	%%	 prints as a single %.
378  *	%n	 exclusive or row & col with 0140.
379  *	%B	 BCD, no output.
380  *	%D	 reverse coding (x-2*(x%16)), no output.
381  */
382 
383     char *
tgoto(char * cm,int col,int line)384 tgoto(
385     char    *cm,				// cm string, from termcap
386     int	    col,				// column, x position
387     int	    line)				// line, y position
388 {
389     char    gx, gy,				// x, y
390 	*ptr,					// pointer in 'cm'
391 	reverse = 0,				// reverse flag
392 	*bufp,					// pointer in returned string
393 	addup = 0,				// add upline
394 	addbak = 0,				// add backup
395 	c;
396     static char buffer[32];
397 
398     if (!cm)
399 	return "OOPS";				// Kludge, but standard
400 
401     bufp = buffer;
402     ptr = cm;
403 
404     while (*ptr) {
405 	if ((c = *ptr++) != '%') {		// normal char
406 	    *bufp++ = c;
407 	} else {				// % escape
408 	    switch(c = *ptr++) {
409 	    case 'd':				// decimal
410 		bufp = _addfmt(bufp, "%d", line);
411 		line = col;
412 		break;
413 	    case '2':				// 2 digit decimal
414 		bufp = _addfmt(bufp, "%02d", line);
415 		line = col;
416 		break;
417 	    case '3':				// 3 digit decimal
418 		bufp = _addfmt(bufp, "%03d", line);
419 		line = col;
420 		break;
421 	    case '>':				// %>xy: if >x, add y
422 		gx = *ptr++;
423 		gy = *ptr++;
424 		if (col>gx) col += gy;
425 		if (line>gx) line += gy;
426 		break;
427 	    case '+':				// %+c: add c
428 		line += *ptr++;
429 	    case '.':				// print x/y
430 		if (line == '\t' ||		// these are
431 		   line == '\n' ||		// chars that
432 		   line == '\004' ||		// UNIX hates
433 		   line == '\0') {
434 		    line++;			// so go to next pos
435 		    if (reverse == (line == col))
436 			addup=1;		// and mark UP
437 		    else
438 			addbak=1;		// or BC
439 		}
440 		*bufp++=line;
441 		line = col;
442 		break;
443 	    case 'r':				// r: reverse
444 		gx = line;
445 		line = col;
446 		col = gx;
447 		reverse = 1;
448 		break;
449 	    case 'i':			// increment (1-origin screen)
450 		col++;
451 		line++;
452 		break;
453 	    case '%':				// %%=% literally
454 		*bufp++='%';
455 		break;
456 	    case 'n':				// magic DM2500 code
457 		line ^= 0140;
458 		col ^= 0140;
459 		break;
460 	    case 'B':				// bcd encoding
461 		line = line/10<<4+line%10;
462 		col = col/10<<4+col%10;
463 		break;
464 	    case 'D':				// magic Delta Data code
465 		line = line-2*(line&15);
466 		col = col-2*(col&15);
467 		break;
468 	    default:				// Unknown escape
469 		return "OOPS";
470 	    }
471 	}
472     }
473 
474     if (addup)					// add upline
475 	if (UP) {
476 	    ptr=UP;
477 	    while (VIM_ISDIGIT(*ptr) || *ptr == '.')
478 		ptr++;
479 	    if (*ptr == '*')
480 		ptr++;
481 	    while (*ptr)
482 		*bufp++ = *ptr++;
483 	}
484 
485     if (addbak)					// add backspace
486 	if (BC) {
487 	    ptr=BC;
488 	    while (VIM_ISDIGIT(*ptr) || *ptr == '.')
489 		ptr++;
490 	    if (*ptr == '*')
491 		ptr++;
492 	    while (*ptr)
493 		*bufp++ = *ptr++;
494 	}
495 	else
496 	    *bufp++='\b';
497 
498     *bufp = 0;
499 
500     return(buffer);
501 }
502 
503 /*
504  * Module: tputs
505  *
506  * Purpose: decode padding information
507  *
508  * Calling conventions: cp = string to be padded, affcnt = # of items affected
509  *	(lines, characters, whatever), outc = routine to output 1 character.
510  *
511  * Returned values: none
512  *
513  * Notes
514  *	cp has padding information ahead of it, in the form
515  *  nnnTEXT or nnn*TEXT. nnn is the number of milliseconds to delay,
516  *  and may be a decimal (nnn.mmm). If the asterisk is given, then
517  *  the delay is multiplied by afcnt. The delay is produced by outputting
518  *  a number of nulls (or other padding char) after printing the
519  *  TEXT.
520  *
521  */
522 
523 long _bauds[16]={
524     0,	50, 75,	110,
525     134,    150,    200,    300,
526     600,    1200,   1800,   2400,
527     4800,   9600,   19200,  19200 };
528 
529     int
tputs(char * cp,int affcnt,void (* outc)(unsigned int))530 tputs(
531     char *cp,				// string to print
532     int affcnt,				// Number of lines affected
533     void (*outc)(unsigned int))		// routine to output 1 character
534 {
535     long    frac,			// 10^(#digits after decimal point)
536 	counter,			// digits
537 	atol(const char *);
538 
539     if (VIM_ISDIGIT(*cp)) {
540 	counter = 0;
541 	frac = 1000;
542 	while (VIM_ISDIGIT(*cp))
543 	    counter = counter * 10L + (long)(*cp++ - '0');
544 	if (*cp == '.')
545 	    while (VIM_ISDIGIT(*++cp)) {
546 		counter = counter * 10L + (long)(*cp++ - '0');
547 		frac = frac * 10;
548 	    }
549 	if (*cp!='*') {			// multiply by affected lines
550 	    if (affcnt>1) affcnt = 1;
551 	}
552 	else
553 	    cp++;
554 
555 	// Calculate number of characters for padding counter/frac ms delay
556 	if (ospeed)
557 	    counter = (counter * _bauds[ospeed] * (long)affcnt) / frac;
558 
559 	while (*cp)			// output string
560 	    (*outc)(*cp++);
561 	if (ospeed)
562 	    while (counter--)		// followed by pad characters
563 		(*outc)(PC);
564     }
565     else
566 	while (*cp)
567 	    (*outc)(*cp++);
568     return 0;
569 }
570 
571 /*
572  * Module: tutil.c
573  *
574  * Purpose: Utility routines for TERMLIB functions.
575  * Returns length of text common to s1 and s2.
576  */
577     static int
_match(char * s1,char * s2)578 _match(char *s1, char *s2)
579 {
580     int i = 0;
581 
582     while (s1[i] && s1[i] == s2[i])
583 	i++;
584 
585     return i;
586 }
587 
588 /*
589  * finds next c in s that's a member of set, returns pointer
590  */
591     static char *
_find(char * s,char * set)592 _find(char *s, char *set)
593 {
594     for (; *s; s++)
595     {
596 	char	*ptr = set;
597 
598 	while (*ptr && *s != *ptr)
599 	    ptr++;
600 
601 	if (*ptr)
602 	    return s;
603     }
604 
605     return s;
606 }
607 
608 /*
609  * add val to buf according to format fmt
610  */
611     static char *
_addfmt(char * buf,char * fmt,int val)612 _addfmt(char *buf, char *fmt, int val)
613 {
614     sprintf(buf, fmt, val);
615     while (*buf)
616 	buf++;
617     return buf;
618 }
619