terminal.c revision 9898
1/*-
2 * Copyright (c) 1992, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Christos Zoulas of Cornell University.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#if !defined(lint) && !defined(SCCSID)
38static char sccsid[] = "@(#)term.c	8.1 (Berkeley) 6/4/93";
39#endif /* not lint && not SCCSID */
40
41/*
42 * term.c: Editor/termcap-curses interface
43 *	   We have to declare a static variable here, since the
44 *	   termcap putchar routine does not take an argument!
45 */
46#include "sys.h"
47#include <stdio.h>
48#include <signal.h>
49#include <string.h>
50#include <stdlib.h>
51#include <unistd.h>
52#include <termcap.h>
53#include <sys/types.h>
54
55#include "el.h"
56
57/*
58 * IMPORTANT NOTE: these routines are allowed to look at the current screen
59 * and the current possition assuming that it is correct.  If this is not
60 * true, then the update will be WRONG!  This is (should be) a valid
61 * assumption...
62 */
63
64#define TC_BUFSIZE 2048
65
66#define GoodStr(a) (el->el_term.t_str[a] != NULL && \
67		    el->el_term.t_str[a][0] != '\0')
68#define Str(a) el->el_term.t_str[a]
69#define Val(a) el->el_term.t_val[a]
70
71private struct {
72    char   *b_name;
73    int     b_rate;
74} baud_rate[] = {
75#ifdef B0
76    { "0", B0 },
77#endif
78#ifdef B50
79    { "50", B50 },
80#endif
81#ifdef B75
82    { "75", B75 },
83#endif
84#ifdef B110
85    { "110", B110 },
86#endif
87#ifdef B134
88    { "134", B134 },
89#endif
90#ifdef B150
91    { "150", B150 },
92#endif
93#ifdef B200
94    { "200", B200 },
95#endif
96#ifdef B300
97    { "300", B300 },
98#endif
99#ifdef B600
100    { "600", B600 },
101#endif
102#ifdef B900
103    { "900", B900 },
104#endif
105#ifdef B1200
106    { "1200", B1200 },
107#endif
108#ifdef B1800
109    { "1800", B1800 },
110#endif
111#ifdef B2400
112    { "2400", B2400 },
113#endif
114#ifdef B3600
115    { "3600", B3600 },
116#endif
117#ifdef B4800
118    { "4800", B4800 },
119#endif
120#ifdef B7200
121    { "7200", B7200 },
122#endif
123#ifdef B9600
124    { "9600", B9600 },
125#endif
126#ifdef EXTA
127    { "19200", EXTA },
128#endif
129#ifdef B19200
130    { "19200", B19200 },
131#endif
132#ifdef EXTB
133    { "38400", EXTB },
134#endif
135#ifdef B38400
136    { "38400", B38400 },
137#endif
138#ifdef B57600
139    { "57600", B57600 },
140#endif
141#ifdef B115200
142    { "115200", B115200 },
143#endif
144    { NULL, 0 }
145};
146
147private struct termcapstr {
148    char   *name;
149    char   *long_name;
150} tstr[] = {
151
152#define T_al	0
153    {	"al",	"add new blank line"		},
154#define T_bl	1
155    {	"bl",	"audible bell"			},
156#define T_cd	2
157    {	"cd",	"clear to bottom"		},
158#define T_ce	3
159    {	"ce",	"clear to end of line"		},
160#define T_ch	4
161    {	"ch",	"cursor to horiz pos"		},
162#define T_cl	5
163    {	"cl",	"clear screen"			},
164#define	T_dc	6
165    {	"dc",	"delete a character"		},
166#define	T_dl	7
167    {	"dl",	"delete a line"		 	},
168#define	T_dm	8
169    {	"dm",	"start delete mode"		},
170#define	T_ed	9
171    {	"ed",	"end delete mode"		},
172#define	T_ei	10
173    {	"ei",	"end insert mode"		},
174#define	T_fs	11
175    {	"fs",	"cursor from status line"	},
176#define	T_ho	12
177    {	"ho",	"home cursor"			},
178#define	T_ic	13
179    {	"ic",	"insert character"		},
180#define	T_im	14
181    {	"im",	"start insert mode"		},
182#define	T_ip	15
183    {	"ip",	"insert padding"		},
184#define	T_kd	16
185    {	"kd",	"sends cursor down"		},
186#define	T_kl	17
187    {	"kl",	"sends cursor left"		},
188#define T_kr	18
189    {	"kr",	"sends cursor right"		},
190#define T_ku	19
191    {	"ku",	"sends cursor up"		},
192#define T_md	20
193    {	"md",	"begin bold"			},
194#define T_me	21
195    {	"me",	"end attributes"		},
196#define T_nd	22
197    {	"nd",	"non destructive space"	 	},
198#define T_se	23
199    {	"se",	"end standout"			},
200#define T_so	24
201    {	"so",	"begin standout"		},
202#define T_ts	25
203    {	"ts",	"cursor to status line"	 	},
204#define T_up	26
205    {	"up",	"cursor up one"		 	},
206#define T_us	27
207    {	"us",	"begin underline"		},
208#define T_ue	28
209    {	"ue",	"end underline"		 	},
210#define T_vb	29
211    {	"vb",	"visible bell"			},
212#define T_DC	30
213    {	"DC",	"delete multiple chars"	 	},
214#define T_DO	31
215    {	"DO",	"cursor down multiple"		},
216#define T_IC	32
217    {	"IC",	"insert multiple chars"	 	},
218#define T_LE	33
219    {	"LE",	"cursor left multiple"		},
220#define T_RI	34
221    {	"RI",	"cursor right multiple"	 	},
222#define T_UP	35
223    {	"UP",	"cursor up multiple"		},
224#define T_str	36
225    {	NULL,   NULL			 	}
226};
227
228private struct termcapval {
229    char   *name;
230    char   *long_name;
231} tval[] = {
232#define T_pt	0
233    {	"pt",	"has physical tabs"	},
234#define T_li	1
235    {	"li",	"Number of lines"	},
236#define T_co	2
237    {	"co",	"Number of columns"	},
238#define T_km	3
239    {	"km",	"Has meta key"		},
240#define T_xt	4
241    {	"xt",	"Tab chars destructive" },
242#define T_MT	5
243    {	"MT",	"Has meta key"		},	/* XXX? */
244#define T_val	6
245    {	NULL, 	NULL,			}
246};
247
248/* do two or more of the attributes use me */
249
250private	void	term_rebuffer_display	__P((EditLine *));
251private	void	term_free_display	__P((EditLine *));
252private	void	term_alloc_display	__P((EditLine *));
253private	void	term_alloc		__P((EditLine *,
254					     struct termcapstr *, char *));
255private void	term_init_arrow		__P((EditLine *));
256private void	term_reset_arrow	__P((EditLine *));
257
258
259private FILE *term_outfile = NULL;	/* XXX: How do we fix that? */
260
261
262/* term_setflags():
263 *	Set the terminal capability flags
264 */
265private void
266term_setflags(el)
267    EditLine *el;
268{
269    EL_FLAGS = 0;
270    if (el->el_tty.t_tabs)
271	EL_FLAGS |= (Val(T_pt) && !Val(T_xt)) ? TERM_CAN_TAB : 0;
272
273    EL_FLAGS |= (Val(T_km) || Val(T_MT)) ? TERM_HAS_META : 0;
274    EL_FLAGS |= GoodStr(T_ce) ? TERM_CAN_CEOL : 0;
275    EL_FLAGS |= (GoodStr(T_dc) || GoodStr(T_DC)) ? TERM_CAN_DELETE : 0;
276    EL_FLAGS |= (GoodStr(T_im) || GoodStr(T_ic) || GoodStr(T_IC)) ?
277		 TERM_CAN_INSERT : 0;
278    EL_FLAGS |= (GoodStr(T_up) || GoodStr(T_UP))  ? TERM_CAN_UP : 0;
279
280    if (GoodStr(T_me) && GoodStr(T_ue))
281	EL_FLAGS |= (strcmp(Str(T_me), Str(T_ue)) == 0) ? TERM_CAN_ME : 0;
282    else
283	EL_FLAGS &= ~TERM_CAN_ME;
284    if (GoodStr(T_me) && GoodStr(T_se))
285	EL_FLAGS |= (strcmp(Str(T_me), Str(T_se)) == 0) ? TERM_CAN_ME : 0;
286
287
288#ifdef DEBUG_SCREEN
289    if (!EL_CAN_UP) {
290	(void) fprintf(el->el_errfile, "WARNING: Your terminal cannot move up.\n");
291	(void) fprintf(el->el_errfile, "Editing may be odd for long lines.\n");
292    }
293    if (!EL_CAN_CEOL)
294	(void) fprintf(el->el_errfile, "no clear EOL capability.\n");
295    if (!EL_CAN_DELETE)
296	(void) fprintf(el->el_errfile, "no delete char capability.\n");
297    if (!EL_CAN_INSERT)
298	(void) fprintf(el->el_errfile, "no insert char capability.\n");
299#endif /* DEBUG_SCREEN */
300}
301
302
303/* term_init():
304 *	Initialize the terminal stuff
305 */
306protected int
307term_init(el)
308    EditLine *el;
309{
310    el->el_term.t_buf = (char *)  el_malloc(TC_BUFSIZE);
311    el->el_term.t_cap = (char *)  el_malloc(TC_BUFSIZE);
312    el->el_term.t_fkey = (fkey_t *) el_malloc(4 * sizeof(fkey_t));
313    el->el_term.t_loc = 0;
314    el->el_term.t_str = (char **) el_malloc(T_str * sizeof(char*));
315    (void) memset(el->el_term.t_str, 0, T_str * sizeof(char*));
316    el->el_term.t_val = (int *)   el_malloc(T_val * sizeof(int));
317    (void) memset(el->el_term.t_val, 0, T_val * sizeof(char*));
318    term_outfile = el->el_outfile;
319    (void) term_set(el, NULL);
320    term_init_arrow(el);
321    return 0;
322}
323
324/* term_end():
325 *	Clean up the terminal stuff
326 */
327protected void
328term_end(el)
329    EditLine *el;
330{
331    el_free((ptr_t) el->el_term.t_buf);
332    el->el_term.t_buf = NULL;
333    el_free((ptr_t) el->el_term.t_cap);
334    el->el_term.t_cap = NULL;
335    el->el_term.t_loc = 0;
336    el_free((ptr_t) el->el_term.t_str);
337    el->el_term.t_str = NULL;
338    el_free((ptr_t) el->el_term.t_val);
339    el->el_term.t_val = NULL;
340    term_free_display(el);
341}
342
343
344/* term_alloc():
345 *	Maintain a string pool for termcap strings
346 */
347private void
348term_alloc(el, t, cap)
349    EditLine *el;
350    struct termcapstr *t;
351    char   *cap;
352{
353    char    termbuf[TC_BUFSIZE];
354    int     tlen, clen;
355    char    **tlist = el->el_term.t_str;
356    char    **tmp, **str = &tlist[t - tstr];
357
358    if (cap == NULL || *cap == '\0') {
359	*str = NULL;
360	return;
361    }
362    else
363	clen = strlen(cap);
364
365    tlen  = *str == NULL ? 0 : strlen(*str);
366
367    /*
368     * New string is shorter; no need to allocate space
369     */
370    if (clen <= tlen) {
371	(void) strcpy(*str, cap);
372	return;
373    }
374
375    /*
376     * New string is longer; see if we have enough space to append
377     */
378    if (el->el_term.t_loc + 3 < TC_BUFSIZE) {
379	(void) strcpy(*str = &el->el_term.t_buf[el->el_term.t_loc], cap);
380	el->el_term.t_loc += clen + 1;	/* one for \0 */
381	return;
382    }
383
384    /*
385     * Compact our buffer; no need to check compaction, cause we know it
386     * fits...
387     */
388    tlen = 0;
389    for (tmp = tlist; tmp < &tlist[T_str]; tmp++)
390	if (*tmp != NULL && *tmp != '\0' && *tmp != *str) {
391	    char   *ptr;
392
393	    for (ptr = *tmp; *ptr != '\0'; termbuf[tlen++] = *ptr++)
394		continue;
395	    termbuf[tlen++] = '\0';
396	}
397    memcpy(el->el_term.t_buf, termbuf, TC_BUFSIZE);
398    el->el_term.t_loc = tlen;
399    if (el->el_term.t_loc + 3 >= TC_BUFSIZE) {
400	(void) fprintf(el->el_errfile, "Out of termcap string space.\n");
401	return;
402    }
403    (void) strcpy(*str = &el->el_term.t_buf[el->el_term.t_loc], cap);
404    el->el_term.t_loc += clen + 1;		/* one for \0 */
405    return;
406} /* end term_alloc */
407
408
409/* term_rebuffer_display():
410 *	Rebuffer the display after the screen changed size
411 */
412private void
413term_rebuffer_display(el)
414    EditLine *el;
415{
416    coord_t *c = &el->el_term.t_size;
417
418    term_free_display(el);
419
420    /* make this public, -1 to avoid wraps */
421    c->h = Val(T_co) - 1;
422    c->v = (EL_BUFSIZ * 4) / c->h + 1;
423
424    term_alloc_display(el);
425} /* end term_rebuffer_display */
426
427
428/* term_alloc_display():
429 *	Allocate a new display.
430 */
431private void
432term_alloc_display(el)
433    EditLine *el;
434{
435    int i;
436    char  **b;
437    coord_t *c = &el->el_term.t_size;
438
439    b = (char **) el_malloc((size_t) (sizeof(char *) * (c->v + 1)));
440    for (i = 0; i < c->v; i++)
441	b[i] = (char *) el_malloc((size_t) (sizeof(char) * (c->h + 1)));
442    b[c->v] = NULL;
443    el->el_display = b;
444
445    b = (char **) el_malloc((size_t) (sizeof(char *) * (c->v + 1)));
446    for (i = 0; i < c->v; i++)
447	b[i] = (char *) el_malloc((size_t) (sizeof(char) * (c->h + 1)));
448    b[c->v] = NULL;
449    el->el_vdisplay = b;
450
451} /* end term_alloc_display */
452
453
454/* term_free_display():
455 *	Free the display buffers
456 */
457private void
458term_free_display(el)
459    EditLine *el;
460{
461    char  **b;
462    char  **bufp;
463
464    b = el->el_display;
465    el->el_display = NULL;
466    if (b != NULL) {
467	for (bufp = b; *bufp != NULL; bufp++)
468	    el_free((ptr_t) *bufp);
469	el_free((ptr_t) b);
470    }
471    b = el->el_vdisplay;
472    el->el_vdisplay = NULL;
473    if (b != NULL) {
474	for (bufp = b; *bufp != NULL; bufp++)
475	    el_free((ptr_t) * bufp);
476	el_free((ptr_t) b);
477    }
478} /* end term_free_display */
479
480
481/* term_move_to_line():
482 *	move to line <where> (first line == 0)
483 * 	as efficiently as possible
484 */
485protected void
486term_move_to_line(el, where)
487    EditLine *el;
488    int     where;
489{
490    int     del, i;
491
492    if (where == el->el_cursor.v)
493	return;
494
495    if (where > el->el_term.t_size.v) {
496#ifdef DEBUG_SCREEN
497	(void) fprintf(el->el_errfile,
498		"term_move_to_line: where is ridiculous: %d\r\n", where);
499#endif /* DEBUG_SCREEN */
500	return;
501    }
502
503    if ((del = where - el->el_cursor.v) > 0) {
504	if ((del > 1) && GoodStr(T_DO))
505	    (void) tputs(tgoto(Str(T_DO), del, del), del, term__putc);
506	else {
507	    for (i = 0; i < del; i++)
508		term__putc('\n');
509	    el->el_cursor.h = 0;	/* because the \n will become \r\n */
510	}
511    }
512    else {			/* del < 0 */
513	if (GoodStr(T_UP) && (-del > 1 || !GoodStr(T_up)))
514	    (void) tputs(tgoto(Str(T_UP), -del, -del), -del, term__putc);
515	else {
516	    if (GoodStr(T_up))
517		for (i = 0; i < -del; i++)
518		    (void) tputs(Str(T_up), 1, term__putc);
519	}
520    }
521    el->el_cursor.v = where;		/* now where is here */
522} /* end term_move_to_line */
523
524
525/* term_move_to_char():
526 *	Move to the character position specified
527 */
528protected void
529term_move_to_char(el, where)
530    EditLine *el;
531    int     where;
532{
533    int     del, i;
534
535mc_again:
536    if (where == el->el_cursor.h)
537	return;
538
539    if (where > (el->el_term.t_size.h + 1)) {
540#ifdef DEBUG_SCREEN
541	(void) fprintf(el->el_errfile,
542		"term_move_to_char: where is riduculous: %d\r\n", where);
543#endif /* DEBUG_SCREEN */
544	return;
545    }
546
547    if (!where) {		/* if where is first column */
548	term__putc('\r');	/* do a CR */
549	el->el_cursor.h = 0;
550	return;
551    }
552
553    del = where - el->el_cursor.h;
554
555    if ((del < -4 || del > 4) && GoodStr(T_ch))
556	/* go there directly */
557	(void) tputs(tgoto(Str(T_ch), where, where), where, term__putc);
558    else {
559	if (del > 0) {		/* moving forward */
560	    if ((del > 4) && GoodStr(T_RI))
561		(void) tputs(tgoto(Str(T_RI), del, del), del, term__putc);
562	    else {
563		if (EL_CAN_TAB) {	/* if I can do tabs, use them */
564		    if ((el->el_cursor.h & 0370) != (where & 0370)) {
565			/* if not within tab stop */
566			for (i = (el->el_cursor.h & 0370);
567			     i < (where & 0370); i += 8)
568			    term__putc('\t');	/* then tab over */
569			el->el_cursor.h = where & 0370;
570		    }
571		}
572		/* it's usually cheaper to just write the chars, so we do. */
573
574		/* NOTE THAT term_overwrite() WILL CHANGE el->el_cursor.h!!! */
575		term_overwrite(el,
576			&el->el_display[el->el_cursor.v][el->el_cursor.h],
577		        where - el->el_cursor.h);
578
579	    }
580	}
581	else {			/* del < 0 := moving backward */
582	    if ((-del > 4) && GoodStr(T_LE))
583		(void) tputs(tgoto(Str(T_LE), -del, -del), -del, term__putc);
584	    else {		/* can't go directly there */
585		/* if the "cost" is greater than the "cost" from col 0 */
586		if (EL_CAN_TAB ? (-del > ((where >> 3) + (where & 07)))
587		    : (-del > where)) {
588		    term__putc('\r');	/* do a CR */
589		    el->el_cursor.h = 0;
590		    goto mc_again;	/* and try again */
591		}
592		for (i = 0; i < -del; i++)
593		    term__putc('\b');
594	    }
595	}
596    }
597    el->el_cursor.h = where;		/* now where is here */
598} /* end term_move_to_char */
599
600
601/* term_overwrite():
602 *	Overstrike num characters
603 */
604protected void
605term_overwrite(el, cp, n)
606    EditLine *el;
607    char *cp;
608    int n;
609{
610    if (n <= 0)
611	return;			/* catch bugs */
612
613    if (n > (el->el_term.t_size.h + 1)) {
614#ifdef DEBUG_SCREEN
615	(void) fprintf(el->el_errfile, "term_overwrite: n is riduculous: %d\r\n", n);
616#endif /* DEBUG_SCREEN */
617	return;
618    }
619
620    do {
621	term__putc(*cp++);
622	el->el_cursor.h++;
623    } while (--n);
624} /* end term_overwrite */
625
626
627/* term_deletechars():
628 *	Delete num characters
629 */
630protected void
631term_deletechars(el, num)
632    EditLine *el;
633    int     num;
634{
635    if (num <= 0)
636	return;
637
638    if (!EL_CAN_DELETE) {
639#ifdef DEBUG_EDIT
640	(void) fprintf(el->el_errfile, "   ERROR: cannot delete   \n");
641#endif /* DEBUG_EDIT */
642	return;
643    }
644
645    if (num > el->el_term.t_size.h) {
646#ifdef DEBUG_SCREEN
647	(void) fprintf(el->el_errfile,
648		"term_deletechars: num is riduculous: %d\r\n", num);
649#endif /* DEBUG_SCREEN */
650	return;
651    }
652
653    if (GoodStr(T_DC))		/* if I have multiple delete */
654	if ((num > 1) || !GoodStr(T_dc)) {	/* if dc would be more expen. */
655	    (void) tputs(tgoto(Str(T_DC), num, num), num, term__putc);
656	    return;
657	}
658
659    if (GoodStr(T_dm))		/* if I have delete mode */
660	(void) tputs(Str(T_dm), 1, term__putc);
661
662    if (GoodStr(T_dc))		/* else do one at a time */
663	while (num--)
664	    (void) tputs(Str(T_dc), 1, term__putc);
665
666    if (GoodStr(T_ed))		/* if I have delete mode */
667	(void) tputs(Str(T_ed), 1, term__putc);
668} /* end term_deletechars */
669
670
671/* term_insertwrite():
672 *	Puts terminal in insert character mode or inserts num
673 *	characters in the line
674 */
675protected void
676term_insertwrite(el, cp, num)
677    EditLine *el;
678    char *cp;
679    int num;
680{
681    if (num <= 0)
682	return;
683    if (!EL_CAN_INSERT) {
684#ifdef DEBUG_EDIT
685	(void) fprintf(el->el_errfile, "   ERROR: cannot insert   \n");
686#endif /* DEBUG_EDIT */
687	return;
688    }
689
690    if (num > el->el_term.t_size.h) {
691#ifdef DEBUG_SCREEN
692	(void) fprintf(el->el_errfile, "StartInsert: num is riduculous: %d\r\n", num);
693#endif /* DEBUG_SCREEN */
694	return;
695    }
696
697    if (GoodStr(T_IC))		/* if I have multiple insert */
698	if ((num > 1) || !GoodStr(T_ic)) {	/* if ic would be more expen. */
699	    (void) tputs(tgoto(Str(T_IC), num, num), num, term__putc);
700	    term_overwrite(el, cp, num);	/* this updates el_cursor.h */
701	    return;
702	}
703
704    if (GoodStr(T_im) && GoodStr(T_ei)) { /* if I have insert mode */
705	(void) tputs(Str(T_im), 1, term__putc);
706
707	el->el_cursor.h += num;
708	do
709	    term__putc(*cp++);
710	while (--num);
711
712	if (GoodStr(T_ip))	/* have to make num chars insert */
713	    (void) tputs(Str(T_ip), 1, term__putc);
714
715	(void) tputs(Str(T_ei), 1, term__putc);
716	return;
717    }
718
719    do {
720	if (GoodStr(T_ic))	/* have to make num chars insert */
721	    (void) tputs(Str(T_ic), 1, term__putc);	/* insert a char */
722
723	term__putc(*cp++);
724
725	el->el_cursor.h++;
726
727	if (GoodStr(T_ip))	/* have to make num chars insert */
728	    (void) tputs(Str(T_ip), 1, term__putc);/* pad the inserted char */
729
730    } while (--num);
731} /* end term_insertwrite */
732
733
734/* term_clear_EOL():
735 *	clear to end of line.  There are num characters to clear
736 */
737protected void
738term_clear_EOL(el, num)
739    EditLine *el;
740    int     num;
741{
742    int i;
743
744    if (EL_CAN_CEOL && GoodStr(T_ce))
745	(void) tputs(Str(T_ce), 1, term__putc);
746    else {
747	for (i = 0; i < num; i++)
748	    term__putc(' ');
749	el->el_cursor.h += num;		/* have written num spaces */
750    }
751} /* end term_clear_EOL */
752
753
754/* term_clear_screen():
755 *	Clear the screen
756 */
757protected void
758term_clear_screen(el)
759    EditLine *el;
760{				/* clear the whole screen and home */
761    if (GoodStr(T_cl))
762	/* send the clear screen code */
763	(void) tputs(Str(T_cl), Val(T_li), term__putc);
764    else if (GoodStr(T_ho) && GoodStr(T_cd)) {
765	(void) tputs(Str(T_ho), Val(T_li), term__putc);	/* home */
766	/* clear to bottom of screen */
767	(void) tputs(Str(T_cd), Val(T_li), term__putc);
768    }
769    else {
770	term__putc('\r');
771	term__putc('\n');
772    }
773} /* end term_clear_screen */
774
775
776/* term_beep():
777 *	Beep the way the terminal wants us
778 */
779protected void
780term_beep(el)
781    EditLine *el;
782{
783    if (GoodStr(T_vb))
784	(void) tputs(Str(T_vb), 1, term__putc);	/* visible bell */
785    else if (GoodStr(T_bl))
786	/* what termcap says we should use */
787	(void) tputs(Str(T_bl), 1, term__putc);
788    else
789	term__putc('\007');	/* an ASCII bell; ^G */
790} /* end term_beep */
791
792
793#ifdef notdef
794/* term_clear_to_bottom():
795 *	Clear to the bottom of the screen
796 */
797protected void
798term_clear_to_bottom(el)
799    EditLine *el;
800{
801    if (GoodStr(T_cd))
802	(void) tputs(Str(T_cd), Val(T_li), term__putc);
803    else if (GoodStr(T_ce))
804	(void) tputs(Str(T_ce), Val(T_li), term__putc);
805} /* end term_clear_to_bottom */
806#endif
807
808
809/* term_set():
810 *	Read in the terminal capabilities from the requested terminal
811 */
812protected int
813term_set(el, term)
814    EditLine *el;
815    char *term;
816{
817    int i;
818    char    buf[TC_BUFSIZE];
819    char   *area;
820    struct termcapstr *t;
821    sigset_t oset, nset;
822    int     lins, cols;
823
824    (void) sigemptyset(&nset);
825    (void) sigaddset(&nset, SIGWINCH);
826    (void) sigprocmask(SIG_BLOCK, &nset, &oset);
827
828    area = buf;
829
830
831    if (term == NULL)
832	term = getenv("TERM");
833
834    if (!term || !term[0])
835	term = "dumb";
836
837    memset(el->el_term.t_cap, 0, TC_BUFSIZE);
838
839    i = tgetent(el->el_term.t_cap, term);
840
841    if (i <= 0) {
842	if (i == -1)
843#ifdef __FreeBSD__
844	    (void) fprintf(el->el_errfile, "Cannot open /usr/share/misc/termcap.\n");
845#else
846	    (void) fprintf(el->el_errfile, "Cannot open /etc/termcap.\n");
847#endif
848	else if (i == 0)
849	    (void) fprintf(el->el_errfile,
850			   "No entry for terminal type \"%s\"\n", term);
851	(void) fprintf(el->el_errfile, "using dumb terminal settings.\n");
852	Val(T_co) = 80;		/* do a dumb terminal */
853	Val(T_pt) = Val(T_km) = Val(T_li) = 0;
854	Val(T_xt) = Val(T_MT);
855	for (t = tstr; t->name != NULL; t++)
856	    term_alloc(el, t, NULL);
857    }
858    else {
859	/* Can we tab */
860	Val(T_pt) = tgetflag("pt");
861	Val(T_xt) = tgetflag("xt");
862	/* do we have a meta? */
863	Val(T_km) = tgetflag("km");
864	Val(T_MT) = tgetflag("MT");
865	/* Get the size */
866	Val(T_co) = tgetnum("co");
867	Val(T_li) = tgetnum("li");
868	for (t = tstr; t->name != NULL; t++)
869	    term_alloc(el, t, tgetstr(t->name, &area));
870    }
871
872    if (Val(T_co) < 2)
873	Val(T_co) = 80;		/* just in case */
874    if (Val(T_li) < 1)
875	Val(T_li) = 24;
876
877    el->el_term.t_size.v = Val(T_co);
878    el->el_term.t_size.h = Val(T_li);
879
880    term_setflags(el);
881
882    (void) term_get_size(el, &lins, &cols);/* get the correct window size */
883    term_change_size(el, lins, cols);
884    (void) sigprocmask(SIG_SETMASK, &oset, NULL);
885    term_bind_arrow(el);
886    return 0;
887} /* end term_set */
888
889
890/* term_get_size():
891 *	Return the new window size in lines and cols, and
892 *	true if the size was changed.
893 */
894protected int
895term_get_size(el, lins, cols)
896    EditLine *el;
897    int    *lins, *cols;
898{
899
900    *cols = Val(T_co);
901    *lins = Val(T_li);
902
903#ifdef TIOCGWINSZ
904    {
905	struct winsize ws;
906	if (ioctl(el->el_infd, TIOCGWINSZ, (ioctl_t) &ws) != -1) {
907	    if (ws.ws_col)
908		*cols = ws.ws_col;
909	    if (ws.ws_row)
910		*lins = ws.ws_row;
911	}
912    }
913#endif
914#ifdef TIOCGSIZE
915    {
916	struct ttysize ts;
917	if (ioctl(el->el_infd, TIOCGSIZE, (ioctl_t) &ts) != -1) {
918	    if (ts.ts_cols)
919		*cols = ts.ts_cols;
920	    if (ts.ts_lines)
921		*lins = ts.ts_lines;
922	}
923    }
924#endif
925    return (Val(T_co) != *cols || Val(T_li) != *lins);
926} /* end term_get_size */
927
928
929/* term_change_size():
930 *	Change the size of the terminal
931 */
932protected void
933term_change_size(el, lins, cols)
934    EditLine *el;
935    int     lins, cols;
936{
937    /*
938     * Just in case
939     */
940    Val(T_co) = (cols < 2) ? 80 : cols;
941    Val(T_li) = (lins < 1) ? 24 : lins;
942
943    term_rebuffer_display(el);		/* re-make display buffers */
944    re_clear_display(el);
945} /* end term_change_size */
946
947
948/* term_init_arrow():
949 *	Initialize the arrow key bindings from termcap
950 */
951private void
952term_init_arrow(el)
953    EditLine *el;
954{
955    fkey_t *arrow = el->el_term.t_fkey;
956
957    arrow[A_K_DN].name    = "down";
958    arrow[A_K_DN].fun.cmd = ED_NEXT_HISTORY;
959    arrow[A_K_DN].type    = XK_CMD;
960
961    arrow[A_K_UP].name    = "up";
962    arrow[A_K_UP].fun.cmd = ED_PREV_HISTORY;
963    arrow[A_K_UP].type    = XK_CMD;
964
965    arrow[A_K_LT].name    = "left";
966    arrow[A_K_LT].fun.cmd = ED_PREV_CHAR;
967    arrow[A_K_LT].type    = XK_CMD;
968
969    arrow[A_K_RT].name    = "right";
970    arrow[A_K_RT].fun.cmd = ED_NEXT_CHAR;
971    arrow[A_K_RT].type    = XK_CMD;
972
973}
974
975
976/* term_reset_arrow():
977 *	Reset arrow key bindings
978 */
979private void
980term_reset_arrow(el)
981    EditLine *el;
982{
983    fkey_t *arrow = el->el_term.t_fkey;
984    static char strA[] = {033, '[', 'A', '\0'};
985    static char strB[] = {033, '[', 'B', '\0'};
986    static char strC[] = {033, '[', 'C', '\0'};
987    static char strD[] = {033, '[', 'D', '\0'};
988    static char stOA[] = {033, 'O', 'A', '\0'};
989    static char stOB[] = {033, 'O', 'B', '\0'};
990    static char stOC[] = {033, 'O', 'C', '\0'};
991    static char stOD[] = {033, 'O', 'D', '\0'};
992
993    key_add(el, strA, &arrow[A_K_UP].fun, arrow[A_K_UP].type);
994    key_add(el, strB, &arrow[A_K_DN].fun, arrow[A_K_DN].type);
995    key_add(el, strC, &arrow[A_K_RT].fun, arrow[A_K_RT].type);
996    key_add(el, strD, &arrow[A_K_LT].fun, arrow[A_K_LT].type);
997    key_add(el, stOA, &arrow[A_K_UP].fun, arrow[A_K_UP].type);
998    key_add(el, stOB, &arrow[A_K_DN].fun, arrow[A_K_DN].type);
999    key_add(el, stOC, &arrow[A_K_RT].fun, arrow[A_K_RT].type);
1000    key_add(el, stOD, &arrow[A_K_LT].fun, arrow[A_K_LT].type);
1001
1002    if (el->el_map.type == MAP_VI) {
1003	key_add(el, &strA[1], &arrow[A_K_UP].fun, arrow[A_K_UP].type);
1004	key_add(el, &strB[1], &arrow[A_K_DN].fun, arrow[A_K_DN].type);
1005	key_add(el, &strC[1], &arrow[A_K_RT].fun, arrow[A_K_RT].type);
1006	key_add(el, &strD[1], &arrow[A_K_LT].fun, arrow[A_K_LT].type);
1007	key_add(el, &stOA[1], &arrow[A_K_UP].fun, arrow[A_K_UP].type);
1008	key_add(el, &stOB[1], &arrow[A_K_DN].fun, arrow[A_K_DN].type);
1009	key_add(el, &stOC[1], &arrow[A_K_RT].fun, arrow[A_K_RT].type);
1010	key_add(el, &stOD[1], &arrow[A_K_LT].fun, arrow[A_K_LT].type);
1011    }
1012}
1013
1014
1015/* term_set_arrow():
1016 *	Set an arrow key binding
1017 */
1018protected int
1019term_set_arrow(el, name, fun, type)
1020    EditLine *el;
1021    char *name;
1022    key_value_t *fun;
1023    int type;
1024{
1025    fkey_t *arrow = el->el_term.t_fkey;
1026    int i;
1027
1028    for (i = 0; i < A_K_NKEYS; i++)
1029	if (strcmp(name, arrow[i].name) == 0) {
1030	    arrow[i].fun  = *fun;
1031	    arrow[i].type = type;
1032	    return 0;
1033	}
1034    return -1;
1035}
1036
1037
1038/* term_clear_arrow():
1039 *	Clear an arrow key binding
1040 */
1041protected int
1042term_clear_arrow(el, name)
1043    EditLine *el;
1044    char *name;
1045{
1046    fkey_t *arrow = el->el_term.t_fkey;
1047    int i;
1048
1049    for (i = 0; i < A_K_NKEYS; i++)
1050	if (strcmp(name, arrow[i].name) == 0) {
1051	    arrow[i].type = XK_NOD;
1052	    return 0;
1053	}
1054    return -1;
1055}
1056
1057
1058/* term_print_arrow():
1059 *	Print the arrow key bindings
1060 */
1061protected void
1062term_print_arrow(el, name)
1063    EditLine *el;
1064    char *name;
1065{
1066    int i;
1067    fkey_t *arrow = el->el_term.t_fkey;
1068
1069    for (i = 0; i < A_K_NKEYS; i++)
1070	if (*name == '\0' || strcmp(name, arrow[i].name) == 0)
1071	    if (arrow[i].type != XK_NOD)
1072		key_kprint(el, arrow[i].name, &arrow[i].fun, arrow[i].type);
1073}
1074
1075
1076/* term_bind_arrow():
1077 *	Bind the arrow keys
1078 */
1079protected void
1080term_bind_arrow(el)
1081    EditLine *el;
1082{
1083    el_action_t *map, *dmap;
1084    int     i, j;
1085    char   *p;
1086    fkey_t *arrow = el->el_term.t_fkey;
1087
1088    /* Check if the components needed are initialized */
1089    if (el->el_term.t_buf == NULL || el->el_map.key == NULL)
1090	return;
1091
1092    map  = el->el_map.type == MAP_VI ? el->el_map.alt : el->el_map.key;
1093    dmap = el->el_map.type == MAP_VI ? el->el_map.vic : el->el_map.emacs;
1094
1095    term_reset_arrow(el);
1096
1097    for (i = 0; i < 4; i++) {
1098	p = el->el_term.t_str[arrow[i].key];
1099	if (p && *p) {
1100	    j = (unsigned char) *p;
1101	    /*
1102	     * Assign the arrow keys only if:
1103	     *
1104	     * 1. They are multi-character arrow keys and the user
1105	     *    has not re-assigned the leading character, or
1106	     *    has re-assigned the leading character to be
1107	     *	  ED_SEQUENCE_LEAD_IN
1108	     * 2. They are single arrow keys pointing to an unassigned key.
1109	     */
1110	    if (arrow[i].type == XK_NOD)
1111		key_clear(el, map, p);
1112	    else {
1113		if (p[1] && (dmap[j] == map[j] ||
1114			     map[j] == ED_SEQUENCE_LEAD_IN)) {
1115		    key_add(el, p, &arrow[i].fun, arrow[i].type);
1116		    map[j] = ED_SEQUENCE_LEAD_IN;
1117		}
1118		else if (map[j] == ED_UNASSIGNED) {
1119		    key_clear(el, map, p);
1120		    if (arrow[i].type == XK_CMD)
1121			map[j] = arrow[i].fun.cmd;
1122		    else
1123			key_add(el, p, &arrow[i].fun, arrow[i].type);
1124		}
1125	    }
1126	}
1127    }
1128}
1129
1130
1131/* term__putc():
1132 *	Add a character
1133 */
1134protected int
1135term__putc(c)
1136    int c;
1137{
1138    return fputc(c, term_outfile);
1139} /* end term__putc */
1140
1141
1142/* term__flush():
1143 *	Flush output
1144 */
1145protected void
1146term__flush()
1147{
1148    (void) fflush(term_outfile);
1149} /* end term__flush */
1150
1151
1152/* term_telltc():
1153 *	Print the current termcap characteristics
1154 */
1155protected int
1156/*ARGSUSED*/
1157term_telltc(el, argc, argv)
1158    EditLine *el;
1159    int argc;
1160    char **argv;
1161{
1162    struct termcapstr *t;
1163    char **ts;
1164    char upbuf[EL_BUFSIZ];
1165
1166    (void) fprintf(el->el_outfile, "\n\tYour terminal has the\n");
1167    (void) fprintf(el->el_outfile, "\tfollowing characteristics:\n\n");
1168    (void) fprintf(el->el_outfile, "\tIt has %d columns and %d lines\n",
1169	    Val(T_co), Val(T_li));
1170    (void) fprintf(el->el_outfile,
1171		   "\tIt has %s meta key\n", EL_HAS_META ? "a" : "no");
1172    (void) fprintf(el->el_outfile,
1173		   "\tIt can%suse tabs\n", EL_CAN_TAB ? " " : "not ");
1174#ifdef notyet
1175    (void) fprintf(el->el_outfile, "\tIt %s automatic margins\n",
1176		   (T_Margin&MARGIN_AUTO)? "has": "does not have");
1177    if (T_Margin & MARGIN_AUTO)
1178	(void) fprintf(el->el_outfile, "\tIt %s magic margins\n",
1179			(T_Margin&MARGIN_MAGIC)?"has":"does not have");
1180#endif
1181
1182    for (t = tstr, ts = el->el_term.t_str; t->name != NULL; t++, ts++)
1183	(void) fprintf(el->el_outfile, "\t%25s (%s) == %s\n", t->long_name,
1184		       t->name, *ts && **ts ?
1185			key__decode_str(*ts, upbuf, "") : "(empty)");
1186    (void) fputc('\n', el->el_outfile);
1187    return 0;
1188}
1189
1190
1191/* term_settc():
1192 *	Change the current terminal characteristics
1193 */
1194protected int
1195/*ARGSUSED*/
1196term_settc(el, argc, argv)
1197    EditLine *el;
1198    int argc;
1199    char **argv;
1200{
1201    struct termcapstr *ts;
1202    struct termcapval *tv;
1203    char   *what, *how;
1204
1205    if (argv == NULL || argv[1] == NULL || argv[2] == NULL)
1206	return -1;
1207
1208    what = argv[1];
1209    how = argv[2];
1210
1211    /*
1212     * Do the strings first
1213     */
1214    for (ts = tstr; ts->name != NULL; ts++)
1215	if (strcmp(ts->name, what) == 0)
1216	    break;
1217
1218    if (ts->name != NULL) {
1219	term_alloc(el, ts, how);
1220	term_setflags(el);
1221	return 0;
1222    }
1223
1224    /*
1225     * Do the numeric ones second
1226     */
1227    for (tv = tval; tv->name != NULL; tv++)
1228	if (strcmp(tv->name, what) == 0)
1229	    break;
1230
1231    if (tv->name != NULL) {
1232	if (tv == &tval[T_pt] || tv == &tval[T_km]
1233#ifdef notyet
1234	    || tv == &tval[T_am] || tv == &tval[T_xn]
1235#endif
1236	    ) {
1237	    if (strcmp(how, "yes") == 0)
1238		el->el_term.t_val[tv - tval] = 1;
1239	    else if (strcmp(how, "no") == 0)
1240		el->el_term.t_val[tv - tval] = 0;
1241	    else {
1242		(void) fprintf(el->el_errfile, "settc: Bad value `%s'.\n", how);
1243		return -1;
1244	    }
1245	    term_setflags(el);
1246	    term_change_size(el, Val(T_li), Val(T_co));
1247	    return 0;
1248	}
1249	else {
1250	    el->el_term.t_val[tv - tval] = atoi(how);
1251	    el->el_term.t_size.v = Val(T_co);
1252	    el->el_term.t_size.h = Val(T_li);
1253	    if (tv == &tval[T_co] || tv == &tval[T_li])
1254		term_change_size(el, Val(T_li), Val(T_co));
1255	    return 0;
1256	}
1257    }
1258    return -1;
1259}
1260
1261
1262/* term_echotc():
1263 *	Print the termcap string out with variable substitution
1264 */
1265protected int
1266/*ARGSUSED*/
1267term_echotc(el, argc, argv)
1268    EditLine *el;
1269    int argc;
1270    char **argv;
1271{
1272    char   *cap, *scap;
1273    int     arg_need, arg_cols, arg_rows;
1274    int     verbose = 0, silent = 0;
1275    char   *area;
1276    static char *fmts = "%s\n", *fmtd = "%d\n";
1277    struct termcapstr *t;
1278    char    buf[TC_BUFSIZE];
1279
1280    area = buf;
1281
1282    if (argv == NULL || argv[1] == NULL)
1283	return -1;
1284    argv++;
1285
1286    if (argv[0][0] == '-') {
1287	switch (argv[0][1]) {
1288	case 'v':
1289	    verbose = 1;
1290	    break;
1291	case 's':
1292	    silent = 1;
1293	    break;
1294	default:
1295	    /* stderror(ERR_NAME | ERR_TCUSAGE); */
1296	    break;
1297	}
1298	argv++;
1299    }
1300    if (!*argv || *argv[0] == '\0')
1301	return 0;
1302    if (strcmp(*argv, "tabs") == 0) {
1303	(void) fprintf(el->el_outfile, fmts, EL_CAN_TAB ? "yes" : "no");
1304	return 0;
1305    }
1306    else if (strcmp(*argv, "meta") == 0) {
1307	(void) fprintf(el->el_outfile, fmts, Val(T_km) ? "yes" : "no");
1308	return 0;
1309    }
1310#ifdef notyet
1311    else if (strcmp(*argv, "xn") == 0) {
1312	(void) fprintf(el->el_outfile, fmts, T_Margin & MARGIN_MAGIC ?
1313			"yes" : "no");
1314	return 0;
1315    }
1316    else if (strcmp(*argv, "am") == 0) {
1317	(void) fprintf(el->el_outfile, fmts, T_Margin & MARGIN_AUTO ?
1318			"yes" : "no");
1319	return 0;
1320    }
1321#endif
1322    else if (strcmp(*argv, "baud") == 0) {
1323	int     i;
1324
1325	for (i = 0; baud_rate[i].b_name != NULL; i++)
1326	    if (el->el_tty.t_speed == baud_rate[i].b_rate) {
1327		(void) fprintf(el->el_outfile, fmts, baud_rate[i].b_name);
1328		return 0;
1329	    }
1330	(void) fprintf(el->el_outfile, fmtd, 0);
1331	return 0;
1332    }
1333    else if (strcmp(*argv, "rows") == 0 || strcmp(*argv, "lines") == 0) {
1334	(void) fprintf(el->el_outfile, fmtd, Val(T_li));
1335	return 0;
1336    }
1337    else if (strcmp(*argv, "cols") == 0) {
1338	(void) fprintf(el->el_outfile, fmtd, Val(T_co));
1339	return 0;
1340    }
1341
1342    /*
1343     * Try to use our local definition first
1344     */
1345    scap = NULL;
1346    for (t = tstr; t->name != NULL; t++)
1347	if (strcmp(t->name, *argv) == 0) {
1348	    scap = el->el_term.t_str[t - tstr];
1349	    break;
1350	}
1351    if (t->name == NULL)
1352	scap = tgetstr(*argv, &area);
1353    if (!scap || scap[0] == '\0') {
1354	if (!silent)
1355	    (void) fprintf(el->el_errfile,
1356		"echotc: Termcap parameter `%s' not found.\n", *argv);
1357	return -1;
1358    }
1359
1360    /*
1361     * Count home many values we need for this capability.
1362     */
1363    for (cap = scap, arg_need = 0; *cap; cap++)
1364	if (*cap == '%')
1365	    switch (*++cap) {
1366	    case 'd':
1367	    case '2':
1368	    case '3':
1369	    case '.':
1370	    case '+':
1371		arg_need++;
1372		break;
1373	    case '%':
1374	    case '>':
1375	    case 'i':
1376	    case 'r':
1377	    case 'n':
1378	    case 'B':
1379	    case 'D':
1380		break;
1381	    default:
1382		/*
1383		 * hpux has lot's of them...
1384		 */
1385		if (verbose)
1386		    (void) fprintf(el->el_errfile,
1387			"echotc: Warning: unknown termcap %% `%c'.\n", *cap);
1388		/* This is bad, but I won't complain */
1389		break;
1390	    }
1391
1392    switch (arg_need) {
1393    case 0:
1394	argv++;
1395	if (*argv && *argv[0]) {
1396	    if (!silent)
1397		(void) fprintf(el->el_errfile,
1398		    "echotc: Warning: Extra argument `%s'.\n", *argv);
1399	    return -1;
1400	}
1401	(void) tputs(scap, 1, term__putc);
1402	break;
1403    case 1:
1404	argv++;
1405	if (!*argv || *argv[0] == '\0') {
1406	    if (!silent)
1407		(void) fprintf(el->el_errfile,
1408		    "echotc: Warning: Missing argument.\n");
1409	    return -1;
1410	}
1411	arg_cols = 0;
1412	arg_rows = atoi(*argv);
1413	argv++;
1414	if (*argv && *argv[0]) {
1415	    if (!silent)
1416		(void) fprintf(el->el_errfile,
1417		    "echotc: Warning: Extra argument `%s'.\n", *argv);
1418	    return -1;
1419	}
1420	(void) tputs(tgoto(scap, arg_cols, arg_rows), 1, term__putc);
1421	break;
1422    default:
1423	/* This is wrong, but I will ignore it... */
1424	if (verbose)
1425	    (void) fprintf(el->el_errfile,
1426		"echotc: Warning: Too many required arguments (%d).\n",
1427		arg_need);
1428	/*FALLTHROUGH*/
1429    case 2:
1430	argv++;
1431	if (!*argv || *argv[0] == '\0') {
1432	    if (!silent)
1433		(void) fprintf(el->el_errfile,
1434		    "echotc: Warning: Missing argument.\n");
1435	    return -1;
1436	}
1437	arg_cols = atoi(*argv);
1438	argv++;
1439	if (!*argv || *argv[0] == '\0') {
1440	    if (!silent)
1441		(void) fprintf(el->el_errfile,
1442		    "echotc: Warning: Missing argument.\n");
1443	    return -1;
1444	}
1445	arg_rows = atoi(*argv);
1446	argv++;
1447	if (*argv && *argv[0]) {
1448	    if (!silent)
1449		(void) fprintf(el->el_errfile,
1450		    "echotc: Warning: Extra argument `%s'.\n", *argv);
1451	    return -1;
1452	}
1453	(void) tputs(tgoto(scap, arg_cols, arg_rows), arg_rows, term__putc);
1454	break;
1455    }
1456    return 0;
1457}
1458