| /* vi:set ts=8 sts=4 sw=4 noet: */ |
| /* |
| * The following software is (C) 1984 Peter da Silva, the Mad Australian, in |
| * the public domain. It may be re-distributed for any purpose with the |
| * inclusion of this notice. |
| */ |
| |
| // Modified by Bram Moolenaar for use with VIM - Vi Improved. |
| // A few bugs removed by Olaf 'Rhialto' Seibert. |
| |
| // TERMLIB: Terminal independent database. |
| |
| #include "vim.h" |
| #include "termlib.pro" |
| |
| #if !defined(AMIGA) && !defined(VMS) |
| # include <sgtty.h> |
| #endif |
| |
| static int getent(char *, char *, FILE *, int); |
| static int nextent(char *, FILE *, int); |
| static int _match(char *, char *); |
| static char *_addfmt(char *, char *, int); |
| static char *_find(char *, char *); |
| |
| /* |
| * Global variables for termlib |
| */ |
| |
| char *tent; // Pointer to terminal entry, set by tgetent |
| char PC = 0; // Pad character, default NULL |
| char *UP = 0, *BC = 0; // Pointers to UP and BC strings from database |
| short ospeed; // Baud rate (1-16, 1=300, 16=19200), as in stty |
| |
| /* |
| * Module: tgetent |
| * |
| * Purpose: Get termcap entry for <term> into buffer at <tbuf>. |
| * |
| * Calling conventions: char tbuf[TBUFSZ+], term=canonical name for terminal. |
| * |
| * Returned values: 1 = success, -1 = can't open file, |
| * 0 = can't find terminal. |
| * |
| * Notes: |
| * - Should probably supply static buffer. |
| * - Uses environment variables "TERM" and "TERMCAP". If TERM = term (that is, |
| * if the argument matches the environment) then it looks at TERMCAP. |
| * - If TERMCAP begins with a slash, then it assumes this is the file to |
| * search rather than /etc/termcap. |
| * - If TERMCAP does not begin with a slash, and it matches TERM, then this is |
| * used as the entry. |
| * - This could be simplified considerably for non-UNIX systems. |
| */ |
| |
| #ifndef TERMCAPFILE |
| # ifdef AMIGA |
| # define TERMCAPFILE "s:termcap" |
| # else |
| # ifdef VMS |
| # define TERMCAPFILE "VIMRUNTIME:termcap" |
| # else |
| # define TERMCAPFILE "/etc/termcap" |
| # endif |
| # endif |
| #endif |
| |
| int |
| tgetent( |
| char *tbuf, // Buffer to hold termcap entry, TBUFSZ bytes max |
| char *term) // Name of terminal |
| { |
| char tcbuf[32]; // Temp buffer to handle |
| char *tcptr = tcbuf; // extended entries |
| char *tcap = TERMCAPFILE; // Default termcap file |
| char *tmp; |
| FILE *termcap; |
| int retval = 0; |
| int len; |
| |
| if ((tmp = (char *)mch_getenv((char_u *)"TERMCAP")) != NULL) |
| { |
| if (*tmp == '/') // TERMCAP = name of termcap file |
| { |
| tcap = tmp ; |
| #if defined(AMIGA) |
| // Convert /usr/share/lib/termcap to usr:share/lib/termcap |
| tcap++; |
| tmp = strchr(tcap, '/'); |
| if (tmp) |
| *tmp = ':'; |
| #endif |
| } |
| else // TERMCAP = termcap entry itself |
| { |
| int tlen = strlen(term); |
| |
| while (*tmp && *tmp != ':') // Check if TERM matches |
| { |
| char *nexttmp; |
| |
| while (*tmp == '|') |
| tmp++; |
| nexttmp = _find(tmp, ":|"); // Rhialto |
| if (tmp+tlen == nexttmp && _match(tmp, term) == tlen) |
| { |
| strcpy(tbuf, tmp); |
| tent = tbuf; |
| return 1; |
| } |
| else |
| tmp = nexttmp; |
| } |
| } |
| } |
| if (!(termcap = mch_fopen(tcap, "r"))) |
| { |
| strcpy(tbuf, tcap); |
| return -1; |
| } |
| |
| len = 0; |
| while (getent(tbuf + len, term, termcap, TBUFSZ - len)) |
| { |
| tcptr = tcbuf; // Rhialto |
| if ((term = tgetstr("tc", &tcptr))) // extended entry |
| { |
| rewind(termcap); |
| len = strlen(tbuf); |
| } |
| else |
| { |
| retval = 1; |
| tent = tbuf; // reset it back to the beginning |
| break; |
| } |
| } |
| fclose(termcap); |
| return retval; |
| } |
| |
| static int |
| getent(char *tbuf, char *term, FILE *termcap, int buflen) |
| { |
| char *tptr; |
| int tlen = strlen(term); |
| |
| while (nextent(tbuf, termcap, buflen)) // For each possible entry |
| { |
| tptr = tbuf; |
| while (*tptr && *tptr != ':') // : terminates name field |
| { |
| char *nexttptr; |
| |
| while (*tptr == '|') // | separates names |
| tptr++; |
| nexttptr = _find(tptr, ":|"); // Rhialto |
| if (tptr + tlen == nexttptr && |
| _match(tptr, term) == tlen) // FOUND! |
| { |
| tent = tbuf; |
| return 1; |
| } |
| else // Look for next name |
| tptr = nexttptr; |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| * Read 1 entry from TERMCAP file. |
| */ |
| static int |
| nextent(char *tbuf, FILE *termcap, int buflen) |
| { |
| char *lbuf = tbuf; // lbuf=line buffer |
| // read lines straight into buffer |
| |
| while (lbuf < tbuf+buflen && // There's room and |
| fgets(lbuf, (int)(tbuf+buflen-lbuf), termcap)) // another line |
| { |
| int llen = strlen(lbuf); |
| |
| if (*lbuf == '#') // eat comments |
| continue; |
| if (lbuf[-1] == ':' && // and whitespace |
| lbuf[0] == '\t' && |
| lbuf[1] == ':') |
| { |
| STRMOVE(lbuf, lbuf + 2); |
| llen -= 2; |
| } |
| if (lbuf[llen-2] == '\\') // and continuations |
| lbuf += llen-2; |
| else |
| { |
| lbuf[llen-1]=0; // no continuation, return |
| return 1; |
| } |
| } |
| |
| return 0; // ran into end of file |
| } |
| |
| /* |
| * Module: tgetflag |
| * |
| * Purpose: returns flag true or false as to the existence of a given entry. |
| * used with 'bs', 'am', etc... |
| * |
| * Calling conventions: id is the 2 character capability id. |
| * |
| * Returned values: 1 for success, 0 for failure. |
| */ |
| |
| int |
| tgetflag(char *id) |
| { |
| char buf[256], *ptr = buf; |
| |
| return tgetstr(id, &ptr) ? 1 : 0; |
| } |
| |
| /* |
| * Module: tgetnum |
| * |
| * Purpose: get numeric value such as 'li' or 'co' from termcap. |
| * |
| * Calling conventions: id = 2 character id. |
| * |
| * Returned values: -1 for failure, else numerical value. |
| */ |
| |
| int |
| tgetnum(char *id) |
| { |
| char *ptr, buf[256]; |
| ptr = buf; |
| |
| if (tgetstr(id, &ptr)) |
| return atoi(buf); |
| else |
| return 0; |
| } |
| |
| /* |
| * Module: tgetstr |
| * |
| * Purpose: get terminal capability string from database. |
| * |
| * Calling conventions: id is the two character capability id. |
| * (*buf) points into a hold buffer for the |
| * id. the capability is copied into the buffer |
| * and (*buf) is advanced to point to the next |
| * free byte in the buffer. |
| * |
| * Returned values: 0 = no such entry, otherwise returns original |
| * (*buf) (now a pointer to the string). |
| * |
| * Notes |
| * It also decodes certain escape sequences in the buffer. |
| * they should be obvious from the code: |
| * \E = escape. |
| * \n, \r, \t, \f, \b match the 'c' escapes. |
| * ^x matches control-x (^@...^_). |
| * \nnn matches nnn octal. |
| * \x, where x is anything else, matches x. I differ |
| * from the standard library here, in that I allow ^: to match |
| * :. |
| * |
| */ |
| |
| char * |
| tgetstr(char *id, char **buf) |
| { |
| int len = strlen(id); |
| char *tmp=tent; |
| char *hold; |
| int i; |
| |
| do { |
| tmp = _find(tmp, ":"); // For each field |
| while (*tmp == ':') // skip empty fields |
| tmp++; |
| if (!*tmp) |
| break; |
| |
| if (_match(id, tmp) == len) |
| { |
| tmp += len; // find '=' '@' or '#' |
| if (*tmp == '@') // :xx@: entry for tc |
| return 0; // deleted entry |
| hold= *buf; |
| while (*++tmp && *tmp != ':') // not at end of field |
| { |
| switch(*tmp) |
| { |
| case '\\': // Expand escapes here |
| switch(*++tmp) |
| { |
| case 0: // ignore backslashes |
| tmp--; // at end of entry |
| break; // shouldn't happen |
| case 'e': |
| case 'E': // ESC |
| *(*buf)++ = ESC; |
| break; |
| case 'n': // \n |
| *(*buf)++ = '\n'; |
| break; |
| case 'r': // \r |
| *(*buf)++ = '\r'; |
| break; |
| case 't': // \t |
| *(*buf)++ = '\t'; |
| break; |
| case 'b': // \b |
| *(*buf)++ = '\b'; |
| break; |
| case 'f': // \f |
| *(*buf)++ = '\f'; |
| break; |
| case '0': // \nnn |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| **buf = 0; |
| // get up to three digits |
| for (i = 0; i < 3 && VIM_ISDIGIT(*tmp); ++i) |
| **buf = **buf * 8 + *tmp++ - '0'; |
| (*buf)++; |
| tmp--; |
| break; |
| default: // \x, for all other x |
| *(*buf)++= *tmp; |
| } |
| break; |
| case '^': // control characters |
| ++tmp; |
| *(*buf)++ = Ctrl_chr(*tmp); |
| break; |
| default: |
| *(*buf)++ = *tmp; |
| } |
| } |
| *(*buf)++ = 0; |
| return hold; |
| } |
| } while (*tmp); |
| |
| return 0; |
| } |
| |
| /* |
| * Module: tgoto |
| * |
| * Purpose: decode cm cursor motion string. |
| * |
| * Calling conventions: cm is cursor motion string. line, col, are the |
| * desired destination. |
| * |
| * Returned values: a string pointing to the decoded string, or "OOPS" if it |
| * cannot be decoded. |
| * |
| * Notes |
| * The accepted escapes are: |
| * %d as in printf, 0 origin. |
| * %2, %3 like %02d, %03d in printf. |
| * %. like %c |
| * %+x adds <x> to value, then %. |
| * %>xy if value>x, adds y. No output. |
| * %i increments line& col, no output. |
| * %r reverses order of line&col. No output. |
| * %% prints as a single %. |
| * %n exclusive or row & col with 0140. |
| * %B BCD, no output. |
| * %D reverse coding (x-2*(x%16)), no output. |
| */ |
| |
| char * |
| tgoto( |
| char *cm, // cm string, from termcap |
| int col, // column, x position |
| int line) // line, y position |
| { |
| char gx, gy, // x, y |
| *ptr, // pointer in 'cm' |
| reverse = 0, // reverse flag |
| *bufp, // pointer in returned string |
| addup = 0, // add upline |
| addbak = 0, // add backup |
| c; |
| static char buffer[32]; |
| |
| if (!cm) |
| return "OOPS"; // Kludge, but standard |
| |
| bufp = buffer; |
| ptr = cm; |
| |
| while (*ptr) |
| { |
| if ((c = *ptr++) != '%') // normal char |
| { |
| *bufp++ = c; |
| } |
| else |
| { // % escape |
| switch(c = *ptr++) |
| { |
| case 'd': // decimal |
| bufp = _addfmt(bufp, "%d", line); |
| line = col; |
| break; |
| case '2': // 2 digit decimal |
| bufp = _addfmt(bufp, "%02d", line); |
| line = col; |
| break; |
| case '3': // 3 digit decimal |
| bufp = _addfmt(bufp, "%03d", line); |
| line = col; |
| break; |
| case '>': // %>xy: if >x, add y |
| gx = *ptr++; |
| gy = *ptr++; |
| if (col>gx) col += gy; |
| if (line>gx) line += gy; |
| break; |
| case '+': // %+c: add c |
| line += *ptr++; |
| case '.': // print x/y |
| if (line == '\t' || // these are |
| line == '\n' || // chars that |
| line == '\004' || // UNIX hates |
| line == '\0') |
| { |
| line++; // so go to next pos |
| if (reverse == (line == col)) |
| addup=1; // and mark UP |
| else |
| addbak=1; // or BC |
| } |
| *bufp++=line; |
| line = col; |
| break; |
| case 'r': // r: reverse |
| gx = line; |
| line = col; |
| col = gx; |
| reverse = 1; |
| break; |
| case 'i': // increment (1-origin screen) |
| col++; |
| line++; |
| break; |
| case '%': // %%=% literally |
| *bufp++='%'; |
| break; |
| case 'n': // magic DM2500 code |
| line ^= 0140; |
| col ^= 0140; |
| break; |
| case 'B': // bcd encoding |
| line = line/10<<4+line%10; |
| col = col/10<<4+col%10; |
| break; |
| case 'D': // magic Delta Data code |
| line = line-2*(line&15); |
| col = col-2*(col&15); |
| break; |
| default: // Unknown escape |
| return "OOPS"; |
| } |
| } |
| } |
| |
| if (addup) // add upline |
| if (UP) |
| { |
| ptr=UP; |
| while (VIM_ISDIGIT(*ptr) || *ptr == '.') |
| ptr++; |
| if (*ptr == '*') |
| ptr++; |
| while (*ptr) |
| *bufp++ = *ptr++; |
| } |
| |
| if (addbak) // add backspace |
| if (BC) |
| { |
| ptr=BC; |
| while (VIM_ISDIGIT(*ptr) || *ptr == '.') |
| ptr++; |
| if (*ptr == '*') |
| ptr++; |
| while (*ptr) |
| *bufp++ = *ptr++; |
| } |
| else |
| *bufp++='\b'; |
| |
| *bufp = 0; |
| |
| return(buffer); |
| } |
| |
| /* |
| * Module: tputs |
| * |
| * Purpose: decode padding information |
| * |
| * Calling conventions: cp = string to be padded, affcnt = # of items affected |
| * (lines, characters, whatever), outc = routine to output 1 character. |
| * |
| * Returned values: none |
| * |
| * Notes |
| * cp has padding information ahead of it, in the form |
| * nnnTEXT or nnn*TEXT. nnn is the number of milliseconds to delay, |
| * and may be a decimal (nnn.mmm). If the asterisk is given, then |
| * the delay is multiplied by afcnt. The delay is produced by outputting |
| * a number of nulls (or other padding char) after printing the |
| * TEXT. |
| * |
| */ |
| |
| long _bauds[16]={ |
| 0, 50, 75, 110, |
| 134, 150, 200, 300, |
| 600, 1200, 1800, 2400, |
| 4800, 9600, 19200, 19200 }; |
| |
| int |
| tputs( |
| char *cp, // string to print |
| int affcnt, // Number of lines affected |
| void (*outc)(unsigned int)) // routine to output 1 character |
| { |
| long frac, // 10^(#digits after decimal point) |
| counter, // digits |
| atol(const char *); |
| |
| if (VIM_ISDIGIT(*cp)) |
| { |
| counter = 0; |
| frac = 1000; |
| while (VIM_ISDIGIT(*cp)) |
| counter = counter * 10L + (long)(*cp++ - '0'); |
| if (*cp == '.') |
| while (VIM_ISDIGIT(*++cp)) |
| { |
| counter = counter * 10L + (long)(*cp++ - '0'); |
| frac = frac * 10; |
| } |
| if (*cp!='*') // multiply by affected lines |
| { |
| if (affcnt>1) affcnt = 1; |
| } |
| else |
| cp++; |
| |
| // Calculate number of characters for padding counter/frac ms delay |
| if (ospeed) |
| counter = (counter * _bauds[ospeed] * (long)affcnt) / frac; |
| |
| while (*cp) // output string |
| (*outc)(*cp++); |
| if (ospeed) |
| while (counter--) // followed by pad characters |
| (*outc)(PC); |
| } |
| else |
| while (*cp) |
| (*outc)(*cp++); |
| return 0; |
| } |
| |
| /* |
| * Module: tutil.c |
| * |
| * Purpose: Utility routines for TERMLIB functions. |
| * Returns length of text common to s1 and s2. |
| */ |
| static int |
| _match(char *s1, char *s2) |
| { |
| int i = 0; |
| |
| while (s1[i] && s1[i] == s2[i]) |
| i++; |
| |
| return i; |
| } |
| |
| /* |
| * finds next c in s that's a member of set, returns pointer |
| */ |
| static char * |
| _find(char *s, char *set) |
| { |
| for (; *s; s++) |
| { |
| char *ptr = set; |
| |
| while (*ptr && *s != *ptr) |
| ptr++; |
| |
| if (*ptr) |
| return s; |
| } |
| |
| return s; |
| } |
| |
| /* |
| * add val to buf according to format fmt |
| */ |
| static char * |
| _addfmt(char *buf, char *fmt, int val) |
| { |
| sprintf(buf, fmt, val); |
| while (*buf) |
| buf++; |
| return buf; |
| } |