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