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