/* vi:sw=4:ts=4: * * term.c -- VIM - Vi IMproved * * primitive termcap support added * * NOTE: padding and variable substitution is not performed, * when compiling without TERMCAP, we use tputs() and tgoto() dummies. * * 14.6.92 */ #include "vim.h" #include "globals.h" #include "param.h" #include "proto.h" #ifdef JP #include "jp.h" #endif #ifdef TERMCAP # ifdef linux # include # define TPUTSFUNCAST (outfuntype) # else # define TPUTSFUNCAST # ifdef AMIGA # include "proto/termlib.pro" # endif # endif #endif #ifdef DEBUG # define TTEST(a) debug1("%s: ", "a"); if (a) {debug2("%02x %s\n", *a, a + 1);} else debug("NULL\n"); #endif static void parse_builtin_tcap __ARGS((Tcarr *tc, char *s)); /* * Builtin_tcaps must always contain DFLT_TCAP as the first entry! * DFLT_TCAP is used, when no terminal is specified with -T option or $TERM. * The entries are compact, therefore they normally are included even when * TERMCAP is defined. * When TERMCAP is defined, the builtin entries can be accessed with * "builtin_amiga", "builtin_ansi", "builtin_debug", etc. */ static char *builtin_tcaps[] = { #ifndef NO_BUILTIN_TCAPS DFLT_TCAP, /* almost allways included */ # if !defined(UNIX) && (defined(ALL_BUILTIN_TCAPS) || defined(SOME_BUILTIN_TCAPS)) ANSI_TCAP, /* default for unix */ # endif # if !defined(AMIGA) && (defined(ALL_BUILTIN_TCAPS) || defined(SOME_BUILTIN_TCAPS)) AMIGA_TCAP, /* default for amiga */ # endif # if !defined(MSDOS) && (defined(ALL_BUILTIN_TCAPS) || defined(SOME_BUILTIN_TCAPS)) PCTERM_TCAP, /* default for MSdos */ # endif # if defined(MSDOS) || defined(ALL_BUILTIN_TCAPS) PCANSI_TCAP, # endif # if !defined(ATARI) && defined(ALL_BUILTIN_TCAPS) ATARI_TCAP, /* default for Atari */ # endif # if defined(UNIX) || defined(ALL_BUILTIN_TCAPS) || defined(SOME_BUILTIN_TCAPS) XTERM_TCAP, /* always included on unix */ # endif # ifdef ALL_BUILTIN_TCAPS VT52_TCAP, # endif # if defined(DEBUG) || defined(ALL_BUILTIN_TCAPS) DEBUG_TCAP, /* always included when debugging */ # endif #else /* NO_BUILTIN_TCAPS */ DUMB_TCAP, /* minimal termcap, used when everything else fails */ #endif /* NO_BUILTIN_TCAPS */ NULL, }; /* * Term_strings contains currently used terminal strings. * It is initialized with the default values by parse_builtin_tcap(). * The values can be changed by setting the parameter with the same name. */ Tcarr term_strings; /* * Parsing of the builtin termcap entries. * The terminal's name is not set, as this is already done in termcapinit(). * Chop builtin termcaps, string entries are already '\0' terminated. * not yet implemented: * boolean entries could be empty strings; * numeric entries would need a flag (e.g. high bit of the skip byte), * so that parse_builtin_tcap can handle them. */ static void parse_builtin_tcap(tc, s) Tcarr *tc; char *s; { char **p = &tc->t_name; p++; for (;;) { while (*s++) ; p += *s++; if (!*s) return; *p++ = s; } } #ifdef TERMCAP # ifndef linux /* included in */ # ifndef AMIGA /* included in proto/termlib.pro */ int tgetent(); int tgetnum(); char *tgetstr(); int tputs(); # endif /* AMIGA */ # ifndef hpux extern short ospeed; # endif # endif /* linux */ # ifndef hpux char *UP, *BC, PC; /* should be extern, but some don't have them */ # endif #endif /* TERMCAP */ void set_term(term) char *term; { char **p = builtin_tcaps; #ifdef TERMCAP int builtin = 0; #endif int width = 0, height = 0; if (!strncmp(term, "builtin_", (size_t)8)) { term += 8; #ifdef TERMCAP builtin = 1; #endif } #ifdef TERMCAP else { char *p; static char tstrbuf[TBUFSZ]; char tbuf[TBUFSZ]; char *tp = tstrbuf; int i; i = tgetent(tbuf, term); if (i == -1) { emsg("Cannot open termcap file"); builtin = 1; } else if (i == 0) { emsg("terminal entry not found"); builtin = 1; } else { clear_termparam(); /* clear old parameters */ /* output strings */ T_EL = tgetstr("ce", &tp); T_IL = tgetstr("al", &tp); T_CIL = tgetstr("AL", &tp); T_DL = tgetstr("dl", &tp); T_CDL = tgetstr("DL", &tp); T_ED = tgetstr("cl", &tp); T_CI = tgetstr("vi", &tp); T_CV = tgetstr("ve", &tp); T_TP = tgetstr("me", &tp); T_TI = tgetstr("mr", &tp); /* if 'mr' or 'me' is not defined use 'so' and 'se' */ if (T_TP == NULL || *T_TP == NUL || T_TI == NULL || *T_TI == NUL) { T_TP = tgetstr("se", &tp); T_TI = tgetstr("so", &tp); } #ifdef JPFEP T_UE = tgetstr("ue", &tp); T_US = tgetstr("us", &tp); #endif T_CM = tgetstr("cm", &tp); T_SR = tgetstr("sr", &tp); T_CRI = tgetstr("RI", &tp); T_VB = tgetstr("vb", &tp); T_KS = tgetstr("ks", &tp); T_KE = tgetstr("ke", &tp); T_TS = tgetstr("ti", &tp); T_TE = tgetstr("te", &tp); /* key codes */ term_strings.t_ku = tgetstr("ku", &tp); term_strings.t_kd = tgetstr("kd", &tp); term_strings.t_kl = tgetstr("kl", &tp); /* if cursor-left == backspace, ignore it (televideo 925) */ if (term_strings.t_kl != NULL && *term_strings.t_kl == Ctrl('H')) term_strings.t_kl = NULL; term_strings.t_kr = tgetstr("kr", &tp); /* term_strings.t_sku = tgetstr("", &tp); termcap code unknown */ /* term_strings.t_skd = tgetstr("", &tp); termcap code unknown */ term_strings.t_sku = tgetstr("kP", &tp); /* ken : elvis comp */ term_strings.t_skd = tgetstr("kN", &tp); /* ken : elvis comp */ term_strings.t_sku = NULL; term_strings.t_skd = NULL; term_strings.t_skl = tgetstr("#4", &tp); term_strings.t_skr = tgetstr("%i", &tp); term_strings.t_f1 = tgetstr("k1", &tp); term_strings.t_f2 = tgetstr("k2", &tp); term_strings.t_f3 = tgetstr("k3", &tp); term_strings.t_f4 = tgetstr("k4", &tp); term_strings.t_f5 = tgetstr("k5", &tp); term_strings.t_f6 = tgetstr("k6", &tp); term_strings.t_f7 = tgetstr("k7", &tp); term_strings.t_f8 = tgetstr("k8", &tp); term_strings.t_f9 = tgetstr("k9", &tp); term_strings.t_f10 = tgetstr("k;", &tp); term_strings.t_sf1 = tgetstr("F1", &tp); /* really function keys 11-20 */ term_strings.t_sf2 = tgetstr("F2", &tp); term_strings.t_sf3 = tgetstr("F3", &tp); term_strings.t_sf4 = tgetstr("F4", &tp); term_strings.t_sf5 = tgetstr("F5", &tp); term_strings.t_sf6 = tgetstr("F6", &tp); term_strings.t_sf7 = tgetstr("F7", &tp); term_strings.t_sf8 = tgetstr("F8", &tp); term_strings.t_sf9 = tgetstr("F9", &tp); term_strings.t_sf10 = tgetstr("FA", &tp); term_strings.t_help = tgetstr("%1", &tp); term_strings.t_undo = tgetstr("&8", &tp); height = tgetnum("li"); width = tgetnum("co"); # ifndef hpux BC = tgetstr("bc", &tp); UP = tgetstr("up", &tp); p = tgetstr("pc", &tp); if (p) PC = *p; ospeed = 0; # endif } } if (builtin) #endif { while (*p && strcmp(term, *p)) p++; if (!*p) { #ifndef WIN32 fprintf(stderr, "'%s' not builtin. Available terminals are:\r\n", term); for (p = builtin_tcaps; *p; p++) #ifdef TERMCAP fprintf(stderr, "\tbuiltin_%s\r\n", *p); #else fprintf(stderr, "\t%s\r\n", *p); #endif if (!starting) /* when user typed :set term=xxx, quit here */ { wait_return(TRUE); return; } sleep(2); fprintf(stderr, "defaulting to '%s'\r\n", *builtin_tcaps); sleep(2); #endif /* WIN32 */ p = builtin_tcaps; free(term_strings.t_name); term_strings.t_name = strsave(term = *p); } clear_termparam(); /* clear old parameters */ parse_builtin_tcap(&term_strings, *p); } #ifndef DOSGEN #if defined(AMIGA) || defined(MSDOS) /* DFLT_TCAP indicates that it is the machine console. */ if (strcmp(term, *builtin_tcaps)) term_console = FALSE; else { term_console = TRUE; # ifdef AMIGA win_resize_on(); /* enable window resizing reports */ # endif } #endif #endif ttest(TRUE); /* display initial screen after ttest() checking. jw. */ if (width <= 0 || height <= 0) { /* termcap failed to report size */ /* set defaults, in case mch_get_winsize also fails */ width = 80; #if defined(MSDOS) && !defined(DOSGEN) height = 25; /* console is often 25 lines */ #else height = 24; /* most terminals are 24 lines */ #endif } Rows_max = Rows; /* remember max. physical nr. of Rows */ set_winsize(width, height, FALSE); /* may change Rows_max */ #ifdef DOSGEN Rows_max = Rows; #endif } #if defined(TERMCAP) && (defined(UNIX) || defined(DOSGEN)) /* * Get Columns and Rows from the termcap. Used after a window signal if the * ioctl() fails. It doesn't make sense to call tgetent each time if the "co" * and "li" entries never change. But this may happen on some systems. */ void getlinecol() { char tbuf[TBUFSZ]; if (term_strings.t_name && tgetent(tbuf, term_strings.t_name) > 0) { if (Columns == 0) Columns = tgetnum("co"); if (Rows == 0) Rows = tgetnum("li"); } } #endif static char *tltoa __PARMS((unsigned long)); static char * tltoa(i) unsigned long i; { static char buf[16]; char *p; p = buf + 15; *p = '\0'; do { --p; *p = i % 10 + '0'; i /= 10; } while (i > 0 && p > buf); return p; } #ifndef TERMCAP /* * minimal tgoto() implementation. * no padding and we only parse for %i %d and %+char */ char * tgoto(cm, x, y) char *cm; int x, y; { static char buf[30]; char *p, *s, *e; if (!cm) return "OOPS"; e = buf + 29; for (s = buf; s < e && *cm; cm++) { if (*cm != '%') { *s++ = *cm; continue; } switch (*++cm) { case 'd': p = tltoa((unsigned long)y); y = x; while (*p) *s++ = *p++; break; case 'i': x++; y++; break; case '+': *s++ = (char)(*++cm + y); y = x; break; case '%': *s++ = *cm; break; default: return "OOPS"; } } *s = '\0'; return buf; } #endif /* TERMCAP */ /* * Termcapinit is called from main() to initialize the terminal. * The optional argument is given with the -T command line option. */ void termcapinit(term) char *term; { if (!term) term = (char *)vimgetenv("TERM"); if (!term || !*term) term = *builtin_tcaps; term_strings.t_name = strsave(term); set_term(term); } /* * the number of calls to mch_write is reduced by using the buffer "outbuf" */ #undef BSIZE /* hpux has BSIZE in sys/param.h */ #define BSIZE 2048 static u_char outbuf[BSIZE]; static int bpos = 0; /* number of chars in outbuf */ /* * flushbuf(): flush the output buffer */ void flushbuf() { if (bpos != 0) { mch_write((char *)outbuf, bpos); bpos = 0; } } /* * outchar(c): put a character into the output buffer. * Flush it if it becomes full. */ void outchar(c) unsigned c; { #if defined(UNIX) || defined(DOSGEN) if (c == '\n') /* turn LF into CR-LF (CRMOD does not seem to do this) */ outchar('\r'); #endif outbuf[bpos] = c; ++bpos; if (bpos >= BSIZE) flushbuf(); if (c == '\n') char_count += Columns; else ++char_count; } /* * a never-padding outstr. * use this whenever you don't want to run the string through tputs. * tputs above is harmless, but tputs from the termcap library * is likely to strip off leading digits, that it mistakes for padding * information. (jw) */ void outstrn(s) char *s; { if (bpos > BSIZE - 20) /* avoid terminal strings being split up */ flushbuf(); while (*s) outchar(*s++); } /* * outstr(s): put a string character at a time into the output buffer. * If TERMCAP is defined use the termcap parser. (jw) */ void outstr(s) register char *s; { if (bpos > BSIZE - 20) /* avoid terminal strings being split up */ flushbuf(); if (s) #ifdef TERMCAP tputs(s, 1, TPUTSFUNCAST outchar); #else while (*s) outchar(*s++); #endif } /* * cursor positioning using termcap parser. (jw) */ void windgoto(row, col) int row; int col; { outstr(tgoto(T_CM, col, row)); } /* * Set cursor to current position. * Should be optimized for minimal terminal output. */ void setcursor() { if (!RedrawingDisabled) windgoto(Cursrow, Curscol); } void ttest(pairs) int pairs; { char buf[70]; char *s = "terminal capability %s required.\n"; char *t = NULL; #ifdef TTEST TTEST(T_EL); TTEST(T_IL); TTEST(T_CIL); TTEST(T_DL); TTEST(T_CDL); TTEST(T_ED); TTEST(T_CI); TTEST(T_CV); TTEST(T_TP); TTEST(T_TI); TTEST(T_CM); TTEST(T_SR); TTEST(T_CRI); #endif /* TTEST */ /* hard requirements */ if (!T_ED || !*T_ED) /* erase display */ t = "cl"; if (!T_CM || !*T_CM) /* cursor motion */ t = "cm"; if (t) { sprintf(buf, s, t); emsg(buf); } if (pairs) { /* optional pairs */ if ((!T_TP || !*T_TP) ^ (!T_TI || !*T_TI)) { debug2("cap :me=%s:mr=%s: ignored\n", T_TP, T_TI); T_TP = T_TI = NULL; } if ((!T_CI || !*T_CI) ^ (!T_CV || !*T_CV)) { debug2("cap :vi=%s:ve=%s: ignored\n", T_CI, T_CV); T_CI = T_CV = NULL; } } } /* * inchar() - get one character from * 1. a scriptfile * 2. the keyboard * * As much characters as we can get (upto 'maxlen') are put in buf and * NUL terminated (buffer length must be 'maxlen' + 1). * * If we got an interrupt all input is read until none is available. * * If time == 0 there is no waiting for the char. * If time == n we wait for n msec for a character to arrive. * If time == -1 we wait forever for a character to arrive. * * Return the number of obtained characters. */ int inchar(buf, maxlen, time) char *buf; int maxlen; int time; /* milli seconds */ { int len; int retesc = FALSE; /* return ESC with gotint */ register int c; register int i; #ifdef JP char *top; static int kanji = FALSE; static int tmplen = 0; static char tmp[IOSIZE]; static char round[5] = ""; #endif /* JP */ if (time == -1) /* flush output before blocking */ flushbuf(); did_outofmem_msg = FALSE; /* display out of memory message (again) */ /* * first try script file * If interrupted: Stop reading script files. */ retry: if (scriptin[curscript] != NULL) { if (got_int || (c = getc(scriptin[curscript])) < 0) /* reached EOF */ { /* when reading script file is interrupted, return an ESC to get back to normal mode */ if (got_int) retesc = TRUE; fclose(scriptin[curscript]); scriptin[curscript] = NULL; if (curscript > 0) --curscript; /* recovery may be delayed till after reading a script file */ if (recoverymode) openrecover(); goto retry; /* may read other script if this one was nested */ } if (c == 0) c = K_ZERO; /* replace ^@ with special code */ *buf++ = c; *buf = NUL; return 1; } /* * If we got an interrupt, skip all previously typed characters and * return TRUE if quit reading script file. */ if (got_int) /* skip typed characters */ { while (GetChars(buf, maxlen, T_PEEK)) ; #ifdef JP kanji = FALSE; tmplen = round[0] = 0; #endif /* JP */ return retesc; } #ifdef JP if (State == NORMAL || State == NORMAL_BUSY) { reset_jcount(); kanji = FALSE; tmplen = round[0] = 0; } if (tmplen) /* flush buffer 'tmp' */ { if (tmplen < maxlen) { len = tmplen; memmove(buf, tmp, len); tmplen = 0; } else { len = maxlen; memmove(buf, tmp, len); tmplen -= len; memmove(tmp, tmp + len, tmplen); } return len; } if (round[0]) { int newlen; memmove(buf, round, len = strlen(round)); round[0] = NUL; newlen = GetChars(buf + len, maxlen - len, time); len += newlen; if (newlen == 0) { buf[len] = NUL; return len; } } else len = GetChars(buf, maxlen, time); top = buf; #else /* JP */ len = GetChars(buf, maxlen, time); #endif /* JP */ for (i = len; --i >= 0; ++buf) if (*buf == 0) *(u_char *)buf = K_ZERO; /* replace ^@ with special code */ *buf = NUL; /* add trailing NUL */ #ifdef JP tmplen = kanjiconvsfrom(top, len, tmp, IOSIZE, round, JP_READ, &kanji); if (tmplen > maxlen) { len = maxlen; tmplen -= len; memmove(top, tmp, maxlen); memmove(tmp, tmp + len, tmplen); } else { len = tmplen; tmplen = 0; memmove(top, tmp, len); } if (len == 0 && round[0]) { len = strlen(round); memmove(top, round, len); round[0] = NUL; } top[len] = NUL; #endif /* JP */ return len; } /* * Check if buf[] begins with a terminal key code. * Return 0 for no match, -1 for partial match, > 0 for full match. * With a match the replacement code is put in buf[0], the match is * removed and the number characters in buf is returned. */ int check_termcode(buf) char *buf; { char **p; int slen; int len; len = strlen(buf); for (p = (char **)&term_strings.t_ku; p != (char **)&term_strings.t_undo + 1; ++p) { if (*p == NULL || (slen = strlen(*p)) == 0) /* empty entry */ continue; if (strncmp(*p, buf, (size_t)(slen > len ? len : slen)) == 0) { if (len >= slen) /* got the complete sequence */ { len -= slen; memmove(buf + 1, buf + slen, (size_t)(len + 1)); /* this relies on the Key numbers to be consecutive! */ buf[0] = K_UARROW + (p - (char **)&term_strings.t_ku); return (len + 1); } return -1; /* got a partial sequence */ } } return 0; /* no match found */ } /* * outnum - output a (big) number fast */ void outnum(n) register long n; { outstrn(tltoa((unsigned long)n)); } /* * outnuml - output a (big) number fast and return the number of characters */ int outnuml(n) register long n; { char *s; s = tltoa((unsigned long)n); outstrn(s); return (int)strlen(s); } void check_winsize() { if (Columns < 5) Columns = 5; else if (Columns > MAX_COLUMNS) Columns = MAX_COLUMNS; if (Rows < 2) Rows = 2; p_scroll = Rows >> 1; } /* * set window size * If 'mustset' is TRUE, we must set Rows and Columns, do not get real * window size (this is used for the :win command during recovery). * If 'mustset' is FALSE, we may try to get the real window size and if * it fails use 'width' and 'height'. */ void set_winsize(width, height, mustset) int width, height; int mustset; { register int tmp; if (width < 0 || height < 0) /* just checking... */ return; if (State == HITRETURN || State == SETWSIZE) /* postpone the resizing */ { State = SETWSIZE; return; } screenclear(); #ifdef AMIGA flushbuf(); /* must do this before mch_get_winsize for some obscure reason */ #endif /* AMIGA */ if (mustset || mch_get_winsize()) { debug("mch_get_win failed\n"); Rows = height; Columns = width; mch_set_winsize(); } check_winsize(); /* always check, to get p_scroll right */ if (State == HELP) redrawhelp(); else if (!starting) { tmp = RedrawingDisabled; RedrawingDisabled = FALSE; comp_Botline(); updateScreen(CURSUPD); RedrawingDisabled = tmp; if (State == CMDLINE) redrawcmdline(); else setcursor(); } flushbuf(); } /* * set active window height (for "z" command) */ void set_winheight(height) int height; { if (height > Rows_max) /* can't make it larger */ height = Rows_max; Rows = height; check_winsize(); updateScreen(CLEAR); } void settmode(raw) int raw; { static int oldraw = FALSE; if (oldraw == raw) /* skip if already in desired mode */ return; oldraw = raw; mch_settmode(raw); /* machine specific function */ } void starttermcap() { outstr(T_KS); /* start "keypad transmit" mode */ outstr(T_TS); /* start termcap mode */ flushbuf(); termcap_active = TRUE; } void stoptermcap() { outstr(T_KE); /* stop "keypad transmit" mode */ outstr(T_TE); /* stop termcap mode */ flushbuf(); termcap_active = FALSE; } /* * enable cursor, unless in Visual mode or no inversion possible */ void cursor_on() { if (!Visual.lnum || T_TI == NULL || *T_TI == NUL) outstr(T_CV); } void cursor_off() { outstr(T_CI); /* disable cursor */ }