Mercurial > vim
view src/termlib.c @ 11372:1074f58e1673 v8.0.0571
patch 8.0.0571: negative line number when using :z^ in an empty buffer
commit https://github.com/vim/vim/commit/a364cdb648ae009fa7aa05382f5659335683d349
Author: Bram Moolenaar <Bram@vim.org>
Date: Thu Apr 20 21:12:30 2017 +0200
patch 8.0.0571: negative line number when using :z^ in an empty buffer
Problem: The cursor line number becomes negative when using :z^ in an empty
buffer. (neovim https://github.com/vim/vim/issues/6557)
Solution: Correct the line number. Also reset the column.
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Thu, 20 Apr 2017 21:15:04 +0200 |
parents | 4aead6a9b7a9 |
children | 351cf7c67bbe |
line wrap: on
line source
/* 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) && !defined(MACOS) # 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; }