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) && !defined(RISCOS)
17# include <sgtty.h>
18#endif
19
20static int  getent __ARGS((char *, char *, FILE *, int));
21static int  nextent __ARGS((char *, FILE *, int));
22static int  _match __ARGS((char *, char *));
23static char *_addfmt __ARGS((char *, char *, int));
24static char *_find __ARGS((char *, char *));
25
26/*
27 * Global variables for termlib
28 */
29
30char	*tent;		      /* Pointer to terminal entry, set by tgetent */
31char	PC = 0;		      /* Pad character, default NULL */
32char	*UP = 0, *BC = 0;     /* Pointers to UP and BC strings from database */
33short	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
69tgetent(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
143getent(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
175nextent(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
221tgetflag(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
240tgetnum(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 *
280tgetstr(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 *
390tgoto(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
529long _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
536tputs(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