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