Fix QP encoding canary violation (Peter Hofmann)..
[s-mailx.git] / tty.c
blob2957d85d2046d88f8cbc2484920d0a1790fa0d17
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ TTY interaction.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /* The NCL version is
9 * Copyright (c) 2013 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
11 * Permission to use, copy, modify, and/or distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * Copyright (c) 1980, 1993
25 * The Regents of the University of California. All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 * notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 * notice, this list of conditions and the following disclaimer in the
34 * documentation and/or other materials provided with the distribution.
35 * 3. All advertising materials mentioning features or use of this software
36 * must display the following acknowledgement:
37 * This product includes software developed by the University of
38 * California, Berkeley and its contributors.
39 * 4. Neither the name of the University nor the names of its contributors
40 * may be used to endorse or promote products derived from this software
41 * without specific prior written permission.
43 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
44 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
45 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
46 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
47 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
48 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
49 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
50 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
51 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
52 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
53 * SUCH DAMAGE.
56 #ifndef HAVE_AMALGAMATION
57 # include "nail.h"
58 #endif
60 #ifdef HAVE_READLINE
61 # include <readline/readline.h>
62 # ifdef HAVE_HISTORY
63 # include <readline/history.h>
64 # endif
65 #elif defined HAVE_EDITLINE
66 # include <histedit.h>
67 #endif
69 /* Shared history support macros */
70 #ifdef HAVE_HISTORY
71 # define _CL_HISTFILE(S) \
72 do {\
73 S = ok_vlook(NAIL_HISTFILE);\
74 if ((S) != NULL)\
75 S = fexpand(S, FEXP_LOCAL);\
76 } while (0)
78 # define _CL_HISTSIZE(V) \
79 do {\
80 char const *__sv = ok_vlook(NAIL_HISTSIZE);\
81 long __rv;\
82 if (__sv == NULL || *__sv == '\0' || (__rv = strtol(__sv, NULL, 10)) == 0)\
83 (V) = HIST_SIZE;\
84 else if (__rv < 0)\
85 (V) = 0;\
86 else\
87 (V) = __rv;\
88 } while (0)
90 # define _CL_CHECK_ADDHIST(S,NOACT) \
91 do {\
92 switch (*(S)) {\
93 case '\0':\
94 case ' ':\
95 NOACT;\
96 default:\
97 break;\
99 } while (0)
101 # define C_HISTORY_SHARED \
102 char **argv = v;\
103 long entry;\
104 NYD_ENTER;\
106 if (*argv == NULL)\
107 goto jlist;\
108 if (argv[1] != NULL)\
109 goto jerr;\
110 if (!asccasecmp(*argv, "show"))\
111 goto jlist;\
112 if (!asccasecmp(*argv, "clear"))\
113 goto jclear;\
114 if ((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
115 goto jentry;\
116 jerr:\
117 fprintf(stderr, "Synopsis: history: %s\n",\
118 _("<show> (default), <clear> or select <NO> from editor history"));\
119 v = NULL;\
120 jleave:\
121 NYD_LEAVE;\
122 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
123 #endif /* HAVE_HISTORY */
125 /* fexpand() flags for expand-on-tab */
126 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
129 * Because we have multiple identical implementations, change file layout a bit
130 * and place the implementations one after the other below the other externals
133 static sigjmp_buf __tty_actjmp; /* TODO someday, we won't need it no more */
134 static void
135 __tty_acthdl(int s) /* TODO someday, we won't need it no more */
137 NYD_X; /* Signal handler */
138 termios_state_reset();
139 siglongjmp(__tty_actjmp, s);
142 FL bool_t
143 getapproval(char const * volatile prompt, bool_t noninteract_default)
145 sighandler_type volatile ohdl;
146 bool_t volatile hadsig = FAL0, rv;
147 NYD_ENTER;
149 if (!(options & OPT_INTERACTIVE)) {
150 rv = noninteract_default;
151 goto jleave;
153 rv = FAL0;
155 if (prompt == NULL)
156 prompt = _("Continue (y/n)? ");
158 ohdl = safe_signal(SIGINT, SIG_IGN);
159 if (sigsetjmp(__tty_actjmp, 1) != 0) {
160 hadsig = TRU1;
161 goto jrestore;
163 safe_signal(SIGINT, &__tty_acthdl);
165 if (readline_input(prompt, FAL0, &termios_state.ts_linebuf,
166 &termios_state.ts_linesize, NULL) >= 0)
167 switch (termios_state.ts_linebuf[0]) {
168 case 'N': case 'n': rv = FAL0; break ;
169 default: rv = TRU1; break;
171 jrestore:
172 termios_state_reset();
173 safe_signal(SIGINT, ohdl);
174 jleave:
175 NYD_LEAVE;
176 if (hadsig && ohdl != SIG_IGN)
177 kill(0, SIGINT);
178 return rv;
181 #ifdef HAVE_SOCKETS
182 FL char *
183 getuser(char const * volatile query) /* TODO v15-compat obsolete */
185 sighandler_type volatile ohdl;
186 char * volatile user = NULL;
187 bool_t volatile hadsig = FAL0;
188 NYD_ENTER;
190 if (query == NULL)
191 query = _("User: ");
193 ohdl = safe_signal(SIGINT, SIG_IGN);
194 if (sigsetjmp(__tty_actjmp, 1) != 0) {
195 hadsig = TRU1;
196 goto jrestore;
198 safe_signal(SIGINT, &__tty_acthdl);
200 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
201 &termios_state.ts_linesize, NULL) >= 0)
202 user = termios_state.ts_linebuf;
203 jrestore:
204 termios_state_reset();
205 safe_signal(SIGINT, ohdl);
206 NYD_LEAVE;
207 if (hadsig && ohdl != SIG_IGN)
208 kill(0, SIGINT);
209 return user;
212 FL char *
213 getpassword(char const *query)
215 sighandler_type volatile ohdl;
216 struct termios tios;
217 char * volatile pass = NULL;
218 # if 0
219 bool_t hadsig = FAL0; /* TODO getpassword() no longer reraises SIGINT */
220 # endif
221 NYD_ENTER;
223 if (query == NULL)
224 query = _("Password: ");
225 fputs(query, stdout);
226 fflush(stdout);
228 /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
229 * FIXME foreground pgrp, and can fail with EINTR!! also affects
230 * FIXME termios_state_reset() */
231 if (options & OPT_TTYIN) {
232 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
233 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
234 termios_state.ts_needs_reset = TRU1;
235 tios.c_iflag &= ~(ISTRIP);
236 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
237 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
240 ohdl = safe_signal(SIGINT, SIG_IGN);
241 if (sigsetjmp(__tty_actjmp, 1) != 0) {
242 # if 0
243 hadsig = TRU1;
244 # endif
245 goto jrestore;
247 safe_signal(SIGINT, &__tty_acthdl);
249 if (readline_restart(stdin, &termios_state.ts_linebuf,
250 &termios_state.ts_linesize, 0) >= 0)
251 pass = termios_state.ts_linebuf;
252 jrestore:
253 termios_state_reset();
254 safe_signal(SIGINT, ohdl);
255 if (options & OPT_TTYIN)
256 fputc('\n', stdout);
257 NYD_LEAVE;
258 # if 0
259 if (hadsig && ohdl != SIG_IGN)
260 kill(0, SIGINT);
261 # endif
262 return pass;
264 #endif /* HAVE_SOCKETS */
267 * readline(3)
270 #ifdef HAVE_READLINE
271 static sighandler_type _rl_shup;
272 static char * _rl_buf; /* pre_input() hook: initial line */
273 static int _rl_buflen; /* content, and its length */
275 static int _rl_pre_input(void);
277 static int
278 _rl_pre_input(void)
280 NYD_ENTER;
281 /* Handle leftover data from \ escaped former line */
282 rl_extend_line_buffer(_rl_buflen + 10);
283 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
284 rl_point = rl_end = _rl_buflen;
285 rl_pre_input_hook = (rl_hook_func_t*)NULL;
286 rl_redisplay();
287 NYD_LEAVE;
288 return 0;
291 FL void
292 tty_init(void)
294 # ifdef HAVE_HISTORY
295 long hs;
296 char *v;
297 # endif
298 NYD_ENTER;
300 rl_readline_name = UNCONST(uagent);
301 # ifdef HAVE_HISTORY
302 _CL_HISTSIZE(hs);
303 using_history();
304 stifle_history((int)hs);
305 # endif
306 rl_read_init_file(NULL);
308 /* Because rl_read_init_file() may have introduced yet a different
309 * history size limit, simply load and incorporate the history, leave
310 * it up to readline(3) to do the rest */
311 # ifdef HAVE_HISTORY
312 _CL_HISTFILE(v);
313 if (v != NULL)
314 read_history(v);
315 # endif
316 NYD_LEAVE;
319 FL void
320 tty_destroy(void)
322 # ifdef HAVE_HISTORY
323 char *v;
324 # endif
325 NYD_ENTER;
327 # ifdef HAVE_HISTORY
328 _CL_HISTFILE(v);
329 if (v != NULL)
330 write_history(v);
331 # endif
332 NYD_LEAVE;
335 FL void
336 tty_signal(int sig)
338 sigset_t nset, oset;
339 NYD_X; /* Signal handler */
341 switch (sig) {
342 # ifdef SIGWINCH
343 case SIGWINCH:
344 break;
345 # endif
346 case SIGHUP:
347 /* readline(3) doesn't catch it :( */
348 rl_free_line_state();
349 rl_cleanup_after_signal();
350 safe_signal(SIGHUP, _rl_shup);
351 sigemptyset(&nset);
352 sigaddset(&nset, sig);
353 sigprocmask(SIG_UNBLOCK, &nset, &oset);
354 kill(0, sig);
355 /* XXX When we come here we'll continue editing, so reestablish
356 * XXX cannot happen */
357 sigprocmask(SIG_BLOCK, &oset, NULL);
358 _rl_shup = safe_signal(SIGHUP, &tty_signal);
359 rl_reset_after_signal();
360 break;
361 default:
362 break;
366 FL int
367 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
368 SMALLOC_DEBUG_ARGS)
370 int nn;
371 char *line;
372 NYD_ENTER;
374 if (n > 0) {
375 _rl_buf = *linebuf;
376 _rl_buflen = (int)n;
377 rl_pre_input_hook = &_rl_pre_input;
380 _rl_shup = safe_signal(SIGHUP, &tty_signal);
381 line = readline(prompt != NULL ? prompt : "");
382 safe_signal(SIGHUP, _rl_shup);
384 if (line == NULL) {
385 nn = -1;
386 goto jleave;
388 n = strlen(line);
390 if (n >= *linesize) {
391 *linesize = LINESIZE + n +1;
392 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
394 memcpy(*linebuf, line, n);
395 (free)(line);
396 (*linebuf)[n] = '\0';
397 nn = (int)n;
398 jleave:
399 NYD_LEAVE;
400 return nn;
403 FL void
404 tty_addhist(char const *s, bool_t isgabby)
406 NYD_ENTER;
407 UNUSED(s);
408 UNUSED(isgabby);
409 # ifdef HAVE_HISTORY
410 if (isgabby && !ok_blook(history_gabby))
411 goto jleave;
412 _CL_CHECK_ADDHIST(s, goto jleave);
413 hold_all_sigs(); /* XXX too heavy */
414 add_history(s); /* XXX yet we jump away! */
415 rele_all_sigs(); /* XXX remove jumps */
416 jleave:
417 # endif
418 NYD_LEAVE;
421 # ifdef HAVE_HISTORY
422 FL int
423 c_history(void *v)
425 C_HISTORY_SHARED;
427 jlist: {
428 FILE *fp;
429 HISTORY_STATE *hs;
430 HIST_ENTRY **hl;
431 ul_it i, b;
433 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
434 NULL) {
435 perror("tmpfile");
436 v = NULL;
437 goto jleave;
440 hs = history_get_history_state();
442 for (i = (ul_it)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
443 char *cp = (*--hl)->line;
444 size_t sl = strlen(cp);
445 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n", i, cp, b, sl);
446 b += sl;
449 page_or_print(fp, (size_t)hs->length);
450 Fclose(fp);
452 goto jleave;
454 jclear:
455 clear_history();
456 goto jleave;
458 jentry: {
459 HISTORY_STATE *hs = history_get_history_state();
461 if (UICMP(z, entry, <=, hs->length))
462 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
463 else
464 v = NULL;
466 goto jleave;
468 # endif /* HAVE_HISTORY */
469 #endif /* HAVE_READLINE */
472 * BSD editline(3)
475 #ifdef HAVE_EDITLINE
476 static EditLine * _el_el; /* editline(3) handle */
477 static char const * _el_prompt; /* Current prompt */
478 # ifdef HAVE_HISTORY
479 static History * _el_hcom; /* History handle for commline */
480 # endif
482 static char const * _el_getprompt(void);
484 static char const *
485 _el_getprompt(void)
487 return _el_prompt;
490 FL void
491 tty_init(void)
493 # ifdef HAVE_HISTORY
494 HistEvent he;
495 long hs;
496 char *v;
497 # endif
498 NYD_ENTER;
500 # ifdef HAVE_HISTORY
501 _CL_HISTSIZE(hs);
502 _el_hcom = history_init();
503 history(_el_hcom, &he, H_SETSIZE, (int)hs);
504 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
505 # endif
507 _el_el = el_init(uagent, stdin, stdout, stderr);
508 el_set(_el_el, EL_SIGNAL, 1);
509 el_set(_el_el, EL_TERMINAL, NULL);
510 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
511 # ifdef HAVE_HISTORY
512 el_set(_el_el, EL_HIST, &history, _el_hcom);
513 # endif
514 el_set(_el_el, EL_EDITOR, "emacs");
515 # ifdef EL_PROMPT_ESC
516 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
517 # else
518 el_set(_el_el, EL_PROMPT, &_el_getprompt);
519 # endif
520 # if 0
521 el_set(_el_el, EL_ADDFN, "tab_complete",
522 "editline(3) internal completion function", &_el_file_cpl);
523 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
524 # endif
525 # ifdef HAVE_HISTORY
526 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
527 # endif
528 el_source(_el_el, NULL); /* Source ~/.editrc */
530 /* Because el_source() may have introduced yet a different history size
531 * limit, simply load and incorporate the history, leave it up to
532 * editline(3) to do the rest */
533 # ifdef HAVE_HISTORY
534 _CL_HISTFILE(v);
535 if (v != NULL)
536 history(_el_hcom, &he, H_LOAD, v);
537 # endif
538 NYD_LEAVE;
541 FL void
542 tty_destroy(void)
544 # ifdef HAVE_HISTORY
545 HistEvent he;
546 char *v;
547 # endif
548 NYD_ENTER;
550 el_end(_el_el);
552 # ifdef HAVE_HISTORY
553 _CL_HISTFILE(v);
554 if (v != NULL)
555 history(_el_hcom, &he, H_SAVE, v);
556 history_end(_el_hcom);
557 # endif
558 NYD_LEAVE;
561 FL void
562 tty_signal(int sig)
564 NYD_X; /* Signal handler */
565 switch (sig) {
566 # ifdef SIGWINCH
567 case SIGWINCH:
568 el_resize(_el_el);
569 break;
570 # endif
571 default:
572 break;
576 FL int
577 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
578 SMALLOC_DEBUG_ARGS)
580 int nn;
581 char const *line;
582 NYD_ENTER;
584 _el_prompt = (prompt != NULL) ? prompt : "";
585 if (n > 0)
586 el_push(_el_el, *linebuf);
587 line = el_gets(_el_el, &nn);
589 if (line == NULL) {
590 nn = -1;
591 goto jleave;
593 assert(nn >= 0);
594 n = (size_t)nn;
595 if (n > 0 && line[n - 1] == '\n')
596 nn = (int)--n;
598 if (n >= *linesize) {
599 *linesize = LINESIZE + n + 1;
600 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
602 memcpy(*linebuf, line, n);
603 (*linebuf)[n] = '\0';
604 jleave:
605 NYD_LEAVE;
606 return nn;
609 FL void
610 tty_addhist(char const *s, bool_t isgabby)
612 # ifdef HAVE_HISTORY
613 /* Enlarge meaning of unique .. to something that rocks;
614 * xxx unfortunately this is expensive to do with editline(3)
615 * xxx maybe it would be better to hook the ptfs instead? */
616 HistEvent he;
617 int i;
618 # endif
619 NYD_ENTER;
620 UNUSED(s);
621 UNUSED(isgabby);
623 # ifdef HAVE_HISTORY
624 if (isgabby && !ok_blook(history_gabby))
625 goto jleave;
626 _CL_CHECK_ADDHIST(s, goto jleave);
628 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
629 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
630 i = history(_el_hcom, &he, H_NEXT))
631 if (!strcmp(he.str, s)) {
632 history(_el_hcom, &he, H_DEL, he.num);
633 break;
635 history(_el_hcom, &he, H_ENTER, s);
636 rele_all_sigs(); /* XXX remove jumps */
637 jleave:
638 # endif
639 NYD_LEAVE;
642 # ifdef HAVE_HISTORY
643 FL int
644 c_history(void *v)
646 C_HISTORY_SHARED;
648 jlist: {
649 HistEvent he;
650 FILE *fp;
651 size_t i, b;
652 int x;
654 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
655 NULL) {
656 perror("tmpfile");
657 v = NULL;
658 goto jleave;
661 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
662 b = 0;
663 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
664 x = history(_el_hcom, &he, H_NEXT)) {
665 size_t sl = strlen(he.str);
666 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n",
667 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
668 --i;
669 b += sl;
672 page_or_print(fp, i);
673 Fclose(fp);
675 goto jleave;
677 jclear: {
678 HistEvent he;
679 history(_el_hcom, &he, H_CLEAR);
681 goto jleave;
683 jentry: {
684 HistEvent he;
685 size_t i;
686 int x;
688 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
689 if (UICMP(z, entry, <=, i)) {
690 entry = (long)i - entry;
691 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
692 x = history(_el_hcom, &he, H_NEXT))
693 if (entry-- == 0) {
694 v = temporary_arg_v_store = UNCONST(he.str);
695 goto jleave;
698 v = NULL;
700 goto jleave;
702 # endif /* HAVE_HISTORY */
703 #endif /* HAVE_EDITLINE */
706 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
708 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
709 * We do not handle character widths because the terminal must deal with that
710 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
711 * characters by definition on the other. We're addicted.
713 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
714 * we're forced to use the very same buffer--the one that is passed through to
715 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
716 * convert that on-the-fly back to the plain char* result once we're done.
717 * To simplify our live, use savestr() buffers for all other needed memory
721 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
722 * TODO NCL: during handler de-/installation handling.
725 #ifdef HAVE_NCL
726 # ifndef MAX_INPUT
727 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
728 # endif
730 /* Since we simply fputs(3) the prompt, assume each character requires two
731 * visual cells -- and we need to restrict the maximum prompt size because
732 * of MAX_INPUT and our desire to have room for some error message left */
733 # define _PROMPT_VLEN(P) (strlen(P) * 2)
734 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
736 union xsighdl {
737 sighandler_type shdl; /* Try avoid races by setting */
738 sl_it sint; /* .sint=-1 when inactive */
740 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
742 struct xtios {
743 struct termios told;
744 struct termios tnew;
747 struct cell {
748 wchar_t wc;
749 ui32_t count;
750 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
753 struct line {
754 size_t cursor; /* Current cursor position */
755 size_t topins; /* Outermost cursor col set */
756 union {
757 char *cbuf; /* *x_buf */
758 struct cell *cells;
759 } line;
760 struct str defc; /* Current default content */
761 struct str savec; /* Saved default content */
762 # ifdef HAVE_HISTORY
763 struct hist *hist; /* History cursor */
764 # endif
765 char const *prompt;
766 char const *nd; /* Cursor right */
767 char **x_buf; /* Caller pointers */
768 size_t *x_bufsize;
771 # ifdef HAVE_HISTORY
772 struct hist {
773 struct hist *older;
774 struct hist *younger;
775 ui32_t isgabby : 1;
776 ui32_t len : 31;
777 char dat[VFIELD_SIZE(sizeof(ui32_t))];
779 # endif
781 static union xsighdl _ncl_oint;
782 static union xsighdl _ncl_oquit;
783 static union xsighdl _ncl_oterm;
784 static union xsighdl _ncl_ohup;
785 static union xsighdl _ncl_otstp;
786 static union xsighdl _ncl_ottin;
787 static union xsighdl _ncl_ottou;
788 static struct xtios _ncl_tios;
789 # ifdef HAVE_HISTORY
790 static struct hist *_ncl_hist;
791 static struct hist *_ncl_hist_tail;
792 static size_t _ncl_hist_size;
793 static size_t _ncl_hist_size_max;
794 static bool_t _ncl_hist_load;
795 # endif
797 static void _ncl_sigs_up(void);
798 static void _ncl_sigs_down(void);
800 static void _ncl_term_mode(bool_t raw);
802 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
803 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
804 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
805 static ssize_t _ncl_cell2dat(struct line *l);
806 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
807 static void _ncl_cell2save(struct line *l);
808 # endif
810 static void _ncl_khome(struct line *l, bool_t dobell);
811 static void _ncl_kend(struct line *l);
812 static void _ncl_kbs(struct line *l);
813 static void _ncl_kkill(struct line *l, bool_t dobell);
814 static ssize_t _ncl_keof(struct line *l);
815 static void _ncl_kleft(struct line *l);
816 static void _ncl_kright(struct line *l);
817 static void _ncl_krefresh(struct line *l);
818 static void _ncl_kbwddelw(struct line *l);
819 static void _ncl_kgow(struct line *l, ssize_t dir);
820 static void _ncl_kother(struct line *l, wchar_t wc);
821 # ifdef HAVE_HISTORY
822 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
823 static size_t _ncl_khist(struct line *l, bool_t backwd);
824 static size_t _ncl_krhist(struct line *l);
825 # endif
826 # ifdef HAVE_TABEXPAND
827 static size_t _ncl_kht(struct line *l);
828 # endif
829 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
830 size_t len SMALLOC_DEBUG_ARGS);
832 static void
833 _ncl_sigs_up(void)
835 NYD2_ENTER;
836 if (_ncl_oint.sint == -1)
837 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
838 if (_ncl_oquit.sint == -1)
839 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
840 if (_ncl_oterm.sint == -1)
841 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
842 if (_ncl_ohup.sint == -1)
843 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
844 if (_ncl_otstp.sint == -1)
845 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
846 if (_ncl_ottin.sint == -1)
847 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
848 if (_ncl_ottou.sint == -1)
849 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
850 NYD2_LEAVE;
853 static void
854 _ncl_sigs_down(void)
856 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
857 sighandler_type st;
858 NYD2_ENTER;
860 if (_ncl_ottou.sint != -1) {
861 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
862 safe_signal(SIGTTOU, st);
864 if (_ncl_ottin.sint != -1) {
865 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
866 safe_signal(SIGTTIN, st);
868 if (_ncl_otstp.sint != -1) {
869 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
870 safe_signal(SIGTSTP, st);
872 if (_ncl_ohup.sint != -1) {
873 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
874 safe_signal(SIGHUP, st);
876 if (_ncl_oterm.sint != -1) {
877 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
878 safe_signal(SIGTERM, st);
880 if (_ncl_oquit.sint != -1) {
881 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
882 safe_signal(SIGQUIT, st);
884 if (_ncl_oint.sint != -1) {
885 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
886 safe_signal(SIGINT, st);
888 NYD2_LEAVE;
891 static void
892 _ncl_term_mode(bool_t raw)
894 struct termios *tiosp;
895 NYD2_ENTER;
897 tiosp = &_ncl_tios.told;
898 if (!raw)
899 goto jleave;
901 /* Always requery the attributes, in case we've been moved from background
902 * to foreground or however else in between sessions */
903 tcgetattr(STDIN_FILENO, tiosp);
904 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
905 tiosp = &_ncl_tios.tnew;
906 tiosp->c_cc[VMIN] = 1;
907 tiosp->c_cc[VTIME] = 0;
908 tiosp->c_iflag &= ~(ISTRIP);
909 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
910 jleave:
911 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
912 NYD2_LEAVE;
915 static void
916 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
918 size_t i;
919 NYD2_ENTER;
921 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
922 if (i > *l->x_bufsize) {
923 i <<= 1;
924 *l->x_bufsize = i;
925 l->line.cbuf =
926 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
928 NYD2_LEAVE;
931 static void
932 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
934 size_t j;
935 NYD2_ENTER;
937 if (i > 0)
938 memmove(cap, cap + 1, i * sizeof(*cap));
940 /* And.. the (rest of the) visual update */
941 for (j = 0; j < i; ++j)
942 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
943 fputs(" \b", stdout);
944 for (j = 0; j < i; ++j)
945 putchar('\b');
946 NYD2_LEAVE;
949 static ssize_t
950 _ncl_wboundary(struct line *l, ssize_t dir)
952 size_t c, t, i;
953 struct cell *cap;
954 bool_t anynon;
955 NYD2_ENTER;
957 c = l->cursor;
958 t = l->topins;
959 i = (size_t)-1;
961 if (dir < 0) {
962 if (c == 0)
963 goto jleave;
964 } else if (c == t)
965 goto jleave;
966 else
967 --t, --c; /* Unsigned wrapping may occur (twice), then */
969 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
970 wchar_t wc = cap[c + dir].wc;
971 if (iswblank(wc) || iswpunct(wc)) {
972 if (anynon)
973 break;
974 } else
975 anynon = TRU1;
976 ++i;
977 c += dir;
978 if (dir < 0) {
979 if (c == 0)
980 break;
981 } else if (c == t)
982 break;
984 jleave:
985 NYD2_LEAVE;
986 return (ssize_t)i;
989 static ssize_t
990 _ncl_cell2dat(struct line *l)
992 size_t len = 0, i;
993 NYD2_ENTER;
995 if (l->topins > 0)
996 for (i = 0; i < l->topins; ++i) {
997 struct cell *cap = l->line.cells + i;
998 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
999 len += cap->count;
1001 l->line.cbuf[len] = '\0';
1002 NYD2_LEAVE;
1003 return (ssize_t)len;
1006 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
1007 static void
1008 _ncl_cell2save(struct line *l)
1010 size_t len, i;
1011 struct cell *cap;
1012 NYD2_ENTER;
1014 l->savec.s = NULL, l->savec.l = 0;
1015 if (l->topins == 0)
1016 goto jleave;
1018 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
1019 len += cap->count;
1021 l->savec.l = len;
1022 l->savec.s = salloc(len + 1);
1024 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
1025 memcpy(l->savec.s + len, cap->cbuf, cap->count);
1026 len += cap->count;
1028 l->savec.s[len] = '\0';
1029 jleave:
1030 NYD2_LEAVE;
1032 # endif
1034 static void
1035 _ncl_khome(struct line *l, bool_t dobell)
1037 size_t c;
1038 NYD2_ENTER;
1040 c = l->cursor;
1041 if (c > 0) {
1042 l->cursor = 0;
1043 while (c-- != 0)
1044 putchar('\b');
1045 } else if (dobell)
1046 putchar('\a');
1047 NYD2_LEAVE;
1050 static void
1051 _ncl_kend(struct line *l)
1053 ssize_t i;
1054 NYD2_ENTER;
1056 i = (ssize_t)(l->topins - l->cursor);
1058 if (i > 0) {
1059 l->cursor = l->topins;
1060 while (i-- != 0)
1061 fputs(l->nd, stdout);
1062 } else
1063 putchar('\a');
1064 NYD2_LEAVE;
1067 static void
1068 _ncl_kbs(struct line *l)
1070 ssize_t c, t;
1071 NYD2_ENTER;
1073 c = l->cursor;
1074 t = l->topins;
1076 if (c > 0) {
1077 putchar('\b');
1078 l->cursor = --c;
1079 l->topins = --t;
1080 t -= c;
1081 _ncl_bs_eof_dvup(l->line.cells + c, t);
1082 } else
1083 putchar('\a');
1084 NYD2_LEAVE;
1087 static void
1088 _ncl_kkill(struct line *l, bool_t dobell)
1090 size_t j, c, i;
1091 NYD2_ENTER;
1093 c = l->cursor;
1094 i = (size_t)(l->topins - c);
1096 if (i > 0) {
1097 l->topins = c;
1098 for (j = i; j != 0; --j)
1099 putchar(' ');
1100 for (j = i; j != 0; --j)
1101 putchar('\b');
1102 } else if (dobell)
1103 putchar('\a');
1104 NYD2_LEAVE;
1107 static ssize_t
1108 _ncl_keof(struct line *l)
1110 size_t c, t;
1111 ssize_t i;
1112 NYD2_ENTER;
1114 c = l->cursor;
1115 t = l->topins;
1116 i = (ssize_t)(t - c);
1118 if (i > 0) {
1119 l->topins = --t;
1120 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1121 } else if (t == 0 && !ok_blook(ignoreeof)) {
1122 fputs("^D", stdout);
1123 fflush(stdout);
1124 i = -1;
1125 } else {
1126 putchar('\a');
1127 i = 0;
1129 NYD2_LEAVE;
1130 return i;
1133 static void
1134 _ncl_kleft(struct line *l)
1136 NYD2_ENTER;
1137 if (l->cursor > 0) {
1138 --l->cursor;
1139 putchar('\b');
1140 } else
1141 putchar('\a');
1142 NYD2_LEAVE;
1145 static void
1146 _ncl_kright(struct line *l)
1148 NYD2_ENTER;
1149 if (l->cursor < l->topins) {
1150 ++l->cursor;
1151 fputs(l->nd, stdout);
1152 } else
1153 putchar('\a');
1154 NYD2_LEAVE;
1157 static void
1158 _ncl_krefresh(struct line *l)
1160 struct cell *cap;
1161 size_t i;
1162 NYD2_ENTER;
1164 putchar('\r');
1165 if (l->prompt != NULL && *l->prompt != '\0')
1166 fputs(l->prompt, stdout);
1167 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1168 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1169 for (i = l->topins - l->cursor; i > 0; --i)
1170 putchar('\b');
1171 NYD2_LEAVE;
1174 static void
1175 _ncl_kbwddelw(struct line *l)
1177 ssize_t i;
1178 size_t c, t, j;
1179 struct cell *cap;
1180 NYD2_ENTER;
1182 i = _ncl_wboundary(l, -1);
1183 if (i <= 0) {
1184 if (i < 0)
1185 putchar('\a');
1186 goto jleave;
1189 c = l->cursor - i;
1190 t = l->topins;
1191 l->topins = t - i;
1192 l->cursor = c;
1193 cap = l->line.cells + c;
1195 if (t != l->cursor) {
1196 j = t - c + i;
1197 memmove(cap, cap + i, j * sizeof(*cap));
1200 for (j = i; j > 0; --j)
1201 putchar('\b');
1202 for (j = l->topins - c; j > 0; ++cap, --j)
1203 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1204 for (j = i; j > 0; --j)
1205 putchar(' ');
1206 for (j = t - c; j > 0; --j)
1207 putchar('\b');
1208 jleave:
1209 NYD2_LEAVE;
1212 static void
1213 _ncl_kgow(struct line *l, ssize_t dir)
1215 ssize_t i;
1216 NYD2_ENTER;
1218 i = _ncl_wboundary(l, dir);
1219 if (i <= 0) {
1220 if (i < 0)
1221 putchar('\a');
1222 goto jleave;
1225 if (dir < 0) {
1226 l->cursor -= i;
1227 while (i-- > 0)
1228 putchar('\b');
1229 } else {
1230 l->cursor += i;
1231 while (i-- > 0)
1232 fputs(l->nd, stdout);
1234 jleave:
1235 NYD2_LEAVE;
1238 static void
1239 _ncl_kother(struct line *l, wchar_t wc)
1241 /* Append if at EOL, insert otherwise;
1242 * since we may move around character-wise, always use a fresh ps */
1243 mbstate_t ps;
1244 struct cell cell, *cap;
1245 size_t i, c;
1246 NYD2_ENTER;
1248 /* First init a cell and see wether we'll really handle this wc */
1249 cell.wc = wc;
1250 memset(&ps, 0, sizeof ps);
1251 i = wcrtomb(cell.cbuf, wc, &ps);
1252 if (i > MB_LEN_MAX)
1253 goto jleave;
1254 cell.count = (ui_it)i;
1255 if (enc_has_state) {
1256 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1257 if (i == 1)
1259 else if (--i < MB_LEN_MAX)
1260 cell.count += (ui_it)i;
1261 else
1262 goto jleave;
1265 /* Yes, we will! Place it in the array */
1266 c = l->cursor++;
1267 i = l->topins++ - c;
1268 cap = l->line.cells + c;
1269 if (i > 0)
1270 memmove(cap + 1, cap, i * sizeof(cell));
1271 memcpy(cap, &cell, sizeof cell);
1273 /* And update visual */
1274 c = i;
1276 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1277 while ((++cap, i-- != 0));
1278 while (c-- != 0)
1279 putchar('\b');
1280 jleave:
1281 NYD2_LEAVE;
1284 # ifdef HAVE_HISTORY
1285 static size_t
1286 __ncl_khist_shared(struct line *l, struct hist *hp)
1288 size_t rv;
1289 NYD2_ENTER;
1291 if ((l->hist = hp) != NULL) {
1292 l->defc.s = savestrbuf(hp->dat, hp->len);
1293 rv =
1294 l->defc.l = hp->len;
1295 if (l->topins > 0) {
1296 _ncl_khome(l, FAL0);
1297 _ncl_kkill(l, FAL0);
1299 } else {
1300 putchar('\a');
1301 rv = 0;
1303 NYD2_LEAVE;
1304 return rv;
1307 static size_t
1308 _ncl_khist(struct line *l, bool_t backwd)
1310 struct hist *hp;
1311 size_t rv;
1312 NYD2_ENTER;
1314 /* If we're not in history mode yet, save line content;
1315 * also, disallow forward search, then, and, of course, bail unless we
1316 * do have any history at all */
1317 if ((hp = l->hist) == NULL) {
1318 if (!backwd)
1319 goto jleave;
1320 if ((hp = _ncl_hist) == NULL)
1321 goto jleave;
1322 _ncl_cell2save(l);
1323 goto jleave;
1326 hp = backwd ? hp->older : hp->younger;
1327 jleave:
1328 rv = __ncl_khist_shared(l, hp);
1329 NYD2_LEAVE;
1330 return rv;
1333 static size_t
1334 _ncl_krhist(struct line *l)
1336 struct str orig_savec;
1337 struct hist *hp = NULL;
1338 size_t rv;
1339 NYD2_ENTER;
1341 /* We cannot complete an empty line */
1342 if (l->topins == 0) {
1343 /* XXX The upcoming hard reset would restore a set savec buffer,
1344 * XXX so forcefully reset that. A cleaner solution would be to
1345 * XXX reset it whenever a restore is no longer desired */
1346 l->savec.s = NULL, l->savec.l = 0;
1347 goto jleave;
1349 if ((hp = l->hist) == NULL) {
1350 if ((hp = _ncl_hist) == NULL)
1351 goto jleave;
1352 orig_savec.s = NULL;
1353 orig_savec.l = 0; /* silence CC */
1354 } else if ((hp = hp->older) == NULL)
1355 goto jleave;
1356 else
1357 orig_savec = l->savec;
1359 if (orig_savec.s == NULL)
1360 _ncl_cell2save(l);
1361 for (; hp != NULL; hp = hp->older)
1362 if (is_prefix(l->savec.s, hp->dat))
1363 break;
1364 if (orig_savec.s != NULL)
1365 l->savec = orig_savec;
1366 jleave:
1367 rv = __ncl_khist_shared(l, hp);
1368 NYD2_LEAVE;
1369 return rv;
1371 # endif
1373 # ifdef HAVE_TABEXPAND
1374 static size_t
1375 _ncl_kht(struct line *l)
1377 struct str orig, bot, topp, sub, exp;
1378 struct cell *cword, *ctop, *cx;
1379 bool_t set_savec = FAL0;
1380 size_t rv = 0;
1381 NYD2_ENTER;
1383 /* We cannot expand an empty line */
1384 if (l->topins == 0)
1385 goto jleave;
1387 /* Get plain line data; if this is the first expansion/xy, update the
1388 * very original content so that ^G gets the origin back */
1389 orig = l->savec;
1390 _ncl_cell2save(l);
1391 exp = l->savec;
1392 if (orig.s != NULL)
1393 l->savec = orig;
1394 else
1395 set_savec = TRU1;
1396 orig = exp;
1398 cword = l->line.cells;
1399 ctop = cword + l->cursor;
1401 /* topp: separate data right of cursor */
1402 if ((cx = cword + l->topins) != ctop) {
1403 for (rv = 0; cx > ctop; --cx)
1404 rv += cx->count;
1405 topp.l = rv;
1406 topp.s = orig.s + orig.l - rv;
1407 } else
1408 topp.s = NULL, topp.l = 0;
1410 /* bot, sub: we cannot expand the entire data left of cursor, but only
1411 * the last "word", so separate them */
1412 while (cx > cword && !iswspace(cx[-1].wc))
1413 --cx;
1414 for (rv = 0; cword < cx; ++cword)
1415 rv += cword->count;
1416 sub =
1417 bot = orig;
1418 bot.l = rv;
1419 sub.s += rv;
1420 sub.l -= rv;
1421 sub.l -= topp.l;
1423 /* Leave room for "implicit asterisk" expansion, as below */
1424 if (sub.l == 0) {
1425 sub.s = UNCONST("*");
1426 sub.l = 1;
1427 } else {
1428 exp.s = salloc(sub.l + 1 +1);
1429 memcpy(exp.s, sub.s, sub.l);
1430 exp.s[sub.l] = '\0';
1431 sub.s = exp.s;
1434 /* TODO there is a TODO note upon fexpand() with multi-return;
1435 * TODO if that will change, the if() below can be simplified */
1436 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1437 jredo:
1438 hold_all_sigs();
1439 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1440 rele_all_sigs();
1442 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1443 goto jnope;
1444 /* If the expansion equals the original string, assume the user wants what
1445 * is usually known as tab completion, append `*' and restart */
1446 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1447 if (sub.s[sub.l - 1] == '*')
1448 goto jnope;
1449 sub.s[sub.l++] = '*';
1450 sub.s[sub.l] = '\0';
1451 goto jredo;
1454 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1455 * Take care to take *prompt* into account, since we don't know
1456 * anything about it's visual length (fputs(3) is used), simply
1457 * assume each character requires two columns */
1458 /* TODO the problem is that we loose control otherwise; in the best
1459 * TODO case the user can control via ^A and ^K etc., but be safe;
1460 * TODO we cannot simply adjust fexpand() because we don't know how
1461 * TODO that is implemented... The real solution would be to check
1462 * TODO wether we fit on a line, and start a pager if not.
1463 * TODO However, that should be part of a real tab-COMPLETION, then,
1464 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1465 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1466 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1467 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1468 char const e1[] = "[ERR_TOO_LONG]";
1469 exp.s = UNCONST(e1);
1470 exp.l = sizeof(e1) - 1;
1471 topp.l = 0;
1472 if (rv + bot.l + exp.l >= MAX_INPUT)
1473 bot.l = 0;
1474 if (rv + exp.l >= MAX_INPUT) {
1475 char const e2[] = "[ERR]";
1476 exp.s = UNCONST(e2);
1477 exp.l = sizeof(e2) - 1;
1481 orig.l = bot.l + exp.l + topp.l;
1482 orig.s = salloc(orig.l + 5 +1);
1483 if ((rv = bot.l) > 0)
1484 memcpy(orig.s, bot.s, rv);
1485 memcpy(orig.s + rv, exp.s, exp.l);
1486 rv += exp.l;
1487 if (topp.l > 0) {
1488 memcpy(orig.s + rv, topp.s, topp.l);
1489 rv += topp.l;
1491 orig.s[rv] = '\0';
1493 l->defc = orig;
1494 _ncl_khome(l, FAL0);
1495 _ncl_kkill(l, FAL0);
1496 jleave:
1497 NYD2_LEAVE;
1498 return rv;
1499 jnope:
1500 /* If we've provided a default content, but failed to expand, there is
1501 * nothing we can "revert to": drop that default again */
1502 if (set_savec)
1503 l->savec.s = NULL, l->savec.l = 0;
1504 rv = 0;
1505 goto jleave;
1507 # endif /* HAVE_TABEXPAND */
1509 static ssize_t
1510 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1511 SMALLOC_DEBUG_ARGS)
1513 /* We want to save code, yet we may have to incorporate a lines'
1514 * default content and / or default input to switch back to after some
1515 * history movement; let "len > 0" mean "have to display some data
1516 * buffer", and only otherwise read(2) it */
1517 mbstate_t ps[2];
1518 struct line l;
1519 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp, cursor_maybe, cursor_store;
1520 wchar_t wc;
1521 ssize_t rv;
1522 NYD_ENTER;
1524 memset(&l, 0, sizeof l);
1525 l.line.cbuf = *buf;
1526 if (len != 0) {
1527 l.defc.s = savestrbuf(*buf, len);
1528 l.defc.l = len;
1530 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1531 l.prompt = prompt = "?ERR?";
1532 /* TODO *l.nd=='\0' : instead adjust accmacvar.c to disallow empty vals */
1533 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1534 l.nd = "\033[C"; /* XXX no "magic" constant */
1535 l.x_buf = buf;
1536 l.x_bufsize = bufsize;
1538 if (prompt != NULL && *prompt != '\0') {
1539 fputs(prompt, stdout);
1540 fflush(stdout);
1542 jrestart:
1543 memset(ps, 0, sizeof ps);
1544 cursor_maybe = cursor_store = 0;
1545 /* TODO: NCL: we should output the reset sequence when we jrestart:
1546 * TODO: NCL: if we are using a stateful encoding? !
1547 * TODO: NCL: in short: this is not yet well understood */
1548 for (;;) {
1549 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1551 /* Normal read(2)? Else buffer-takeover: speed this one up */
1552 if (len == 0)
1553 cbufp =
1554 cbuf = cbuf_base;
1555 else {
1556 assert(l.defc.l > 0 && l.defc.s != NULL);
1557 cbufp =
1558 cbuf = l.defc.s + (l.defc.l - len);
1559 cbufp += len;
1562 /* Read in the next complete multibyte character */
1563 for (;;) {
1564 if (len == 0) {
1565 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1566 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1567 continue;
1568 goto jleave;
1570 ++cbufp;
1573 /* Ach! the ISO C multibyte handling!
1574 * Encodings with locking shift states cannot really be helped, since
1575 * it is impossible to only query the shift state, as opposed to the
1576 * entire shift state + character pair (via ISO C functions) */
1577 rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), ps + 0);
1578 if (rv <= 0) {
1579 /* Any error during take-over can only result in a hard reset;
1580 * Otherwise, if it's a hard error, or if too many redundant shift
1581 * sequences overflow our buffer, also perform a hard reset */
1582 if (len != 0 || rv == -1 ||
1583 sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)) {
1584 l.savec.s = l.defc.s = NULL,
1585 l.savec.l = l.defc.l = len = 0;
1586 putchar('\a');
1587 wc = 'G';
1588 goto jreset;
1590 /* Otherwise, due to the way we deal with the buffer, we need to
1591 * restore the mbstate_t from before this conversion */
1592 ps[0] = ps[1];
1593 continue;
1596 if (len != 0 && (len -= (size_t)rv) == 0)
1597 l.defc.s = NULL, l.defc.l = 0;
1598 ps[1] = ps[0];
1599 break;
1602 /* Don't interpret control bytes during buffer take-over */
1603 if (cbuf != cbuf_base)
1604 goto jprint;
1605 switch (wc) {
1606 case 'A' ^ 0x40: /* cursor home */
1607 _ncl_khome(&l, TRU1);
1608 break;
1609 case 'B' ^ 0x40: /* backward character */
1610 j_b:
1611 _ncl_kleft(&l);
1612 break;
1613 /* 'C': interrupt (CTRL-C) */
1614 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1615 if ((rv = _ncl_keof(&l)) < 0)
1616 goto jleave;
1617 break;
1618 case 'E' ^ 0x40: /* end of line */
1619 _ncl_kend(&l);
1620 break;
1621 case 'F' ^ 0x40: /* forward character */
1622 j_f:
1623 _ncl_kright(&l);
1624 break;
1625 /* 'G' below */
1626 case 'H' ^ 0x40: /* backspace */
1627 case '\177':
1628 _ncl_kbs(&l);
1629 break;
1630 case 'I' ^ 0x40: /* horizontal tab */
1631 # ifdef HAVE_TABEXPAND
1632 if ((len = _ncl_kht(&l)) > 0)
1633 goto jrestart;
1634 # endif
1635 goto jbell;
1636 case 'J' ^ 0x40: /* NL (\n) */
1637 goto jdone;
1638 case 'G' ^ 0x40: /* full reset */
1639 jreset:
1640 /* FALLTHRU */
1641 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1642 _ncl_khome(&l, FAL0);
1643 /* FALLTHRU */
1644 case 'K' ^ 0x40: /* kill from cursor to end of line */
1645 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1646 /* (Handle full reset?) */
1647 if (wc == ('G' ^ 0x40)) {
1648 # ifdef HAVE_HISTORY
1649 l.hist = NULL;
1650 # endif
1651 if ((len = l.savec.l) != 0) {
1652 l.defc = l.savec;
1653 l.savec.s = NULL, l.savec.l = 0;
1654 } else
1655 len = l.defc.l;
1657 fflush(stdout);
1658 goto jrestart;
1659 case 'L' ^ 0x40: /* repaint line */
1660 j_l:
1661 _ncl_krefresh(&l);
1662 break;
1663 /* 'M': CR (\r) */
1664 case 'N' ^ 0x40: /* history next */
1665 j_n:
1666 # ifdef HAVE_HISTORY
1667 if (l.hist == NULL)
1668 goto jbell;
1669 if ((len = _ncl_khist(&l, FAL0)) > 0)
1670 goto jrestart;
1671 wc = 'G' ^ 0x40;
1672 goto jreset;
1673 # else
1674 goto jbell;
1675 # endif
1676 /* 'O' */
1677 case 'O' ^ 0x40: /* `dp' */
1678 putchar('\n');
1679 cbuf_base[0] = 'd';
1680 cbuf_base[1] = 'p';
1681 cbuf_base[2] = '\0';
1682 inhook = 0;
1683 execute(cbuf_base, TRU1, 2);
1684 goto j_l;
1685 case 'P' ^ 0x40: /* history previous */
1686 j_p:
1687 # ifdef HAVE_HISTORY
1688 if ((len = _ncl_khist(&l, TRU1)) > 0)
1689 goto jrestart;
1690 wc = 'G' ^ 0x40;
1691 goto jreset;
1692 # else
1693 goto jbell;
1694 # endif
1695 /* 'Q': no code */
1696 case 'R' ^ 0x40: /* reverse history search */
1697 # ifdef HAVE_HISTORY
1698 if ((len = _ncl_krhist(&l)) > 0)
1699 goto jrestart;
1700 wc = 'G' ^ 0x40;
1701 goto jreset;
1702 # else
1703 goto jbell;
1704 # endif
1705 /* 'S': no code */
1706 /* 'U' above */
1707 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1708 case 'W' ^ 0x40: /* backward delete "word" */
1709 _ncl_kbwddelw(&l);
1710 break;
1711 case 'X' ^ 0x40: /* move cursor forward "word" */
1712 _ncl_kgow(&l, +1);
1713 break;
1714 case 'Y' ^ 0x40: /* move cursor backward "word" */
1715 _ncl_kgow(&l, -1);
1716 break;
1717 /* 'Z': suspend (CTRL-Z) */
1718 case 0x1B:
1719 if (cursor_maybe++ != 0)
1720 goto jreset;
1721 continue;
1722 default:
1723 /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */
1724 if (cursor_maybe > 0) {
1725 if (++cursor_maybe == 2) {
1726 if (wc == L'[')
1727 continue;
1728 cursor_maybe = 0;
1729 } else if (cursor_maybe == 3) {
1730 cursor_maybe = 0;
1731 switch (wc) {
1732 default: break;
1733 case L'A': goto j_p;
1734 case L'B': goto j_n;
1735 case L'C': goto j_f;
1736 case L'D': goto j_b;
1737 case L'H':
1738 cursor_store = '0';
1739 goto J_xterm_noapp;
1740 case L'F':
1741 cursor_store = '$';
1742 goto J_xterm_noapp;
1743 case L'1':
1744 case L'4':
1745 case L'5':
1746 case L'6':
1747 cursor_store = ((wc == L'1') ? '0' :
1748 (wc == L'4' ? '$' : (wc == L'5' ? '-' : '+')));
1749 cursor_maybe = 3;
1750 continue;
1752 _ncl_kother(&l, L'[');
1753 } else {
1754 cursor_maybe = 0;
1755 if (wc == L'~')
1756 J_xterm_noapp: {
1757 char x[2];
1758 x[0] = cursor_store;
1759 x[1] = '\0';
1760 putchar('\n');
1761 c_scroll(x);
1762 cursor_store = 0;
1763 goto j_l;
1765 _ncl_kother(&l, L'[');
1766 _ncl_kother(&l, (wchar_t)cursor_store);
1767 cursor_store = 0;
1770 jprint:
1771 if (iswprint(wc)) {
1772 _ncl_kother(&l, wc);
1773 /* Don't clear the history during takeover..
1774 * ..and also avoid fflush()ing unless we've worked entire buffer */
1775 if (len > 0)
1776 continue;
1777 # ifdef HAVE_HISTORY
1778 if (cbuf == cbuf_base)
1779 l.hist = NULL;
1780 # endif
1781 } else {
1782 jbell:
1783 putchar('\a');
1785 break;
1787 fflush(stdout);
1790 /* We have a completed input line, convert the struct cell data to its
1791 * plain character equivalent */
1792 jdone:
1793 putchar('\n');
1794 fflush(stdout);
1795 len = _ncl_cell2dat(&l);
1796 rv = (ssize_t)len;
1797 jleave:
1798 NYD_LEAVE;
1799 return rv;
1802 FL void
1803 tty_init(void)
1805 # ifdef HAVE_HISTORY
1806 long hs;
1807 char *v, *lbuf;
1808 FILE *f;
1809 size_t lsize, cnt, llen;
1810 # endif
1811 NYD_ENTER;
1813 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1814 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1815 _ncl_ottou.sint = -1;
1817 # ifdef HAVE_HISTORY
1818 _CL_HISTSIZE(hs);
1819 _ncl_hist_size = 0;
1820 _ncl_hist_size_max = hs;
1821 if (hs == 0)
1822 goto jleave;
1824 _CL_HISTFILE(v);
1825 if (v == NULL)
1826 goto jleave;
1828 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1829 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1830 if (f == NULL)
1831 goto jdone;
1832 fcntl_lock(fileno(f), FLOCK_READ); /* TODO ouch, retval check, etc. */
1834 lbuf = NULL;
1835 lsize = 0;
1836 cnt = fsize(f);
1837 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1838 if (llen > 0 && lbuf[llen - 1] == '\n')
1839 lbuf[--llen] = '\0';
1840 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1841 continue;
1842 else {
1843 bool_t isgabby = (lbuf[0] == '*');
1844 _ncl_hist_load = TRU1;
1845 tty_addhist(lbuf + isgabby, isgabby);
1846 _ncl_hist_load = FAL0;
1849 if (lbuf != NULL)
1850 free(lbuf);
1852 fclose(f);
1853 jdone:
1854 rele_all_sigs(); /* XXX remove jumps */
1855 jleave:
1856 # endif /* HAVE_HISTORY */
1857 NYD_LEAVE;
1860 FL void
1861 tty_destroy(void)
1863 # ifdef HAVE_HISTORY
1864 long hs;
1865 char *v;
1866 struct hist *hp;
1867 bool_t dogabby;
1868 FILE *f;
1869 # endif
1870 NYD_ENTER;
1872 # ifdef HAVE_HISTORY
1873 _CL_HISTSIZE(hs);
1874 if (hs == 0)
1875 goto jleave;
1876 _CL_HISTFILE(v);
1877 if (v == NULL)
1878 goto jleave;
1880 if ((hp = _ncl_hist) != NULL)
1881 while (hp->older != NULL && hs-- != 0)
1882 hp = hp->older;
1883 dogabby = ok_blook(history_gabby_persist);
1885 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1886 f = fopen(v, "w"); /* TODO temporary + rename?! */
1887 if (f == NULL)
1888 goto jdone;
1889 fcntl_lock(fileno(f), FLOCK_WRITE); /* TODO ouch, retval check, etc. */
1890 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1891 goto jclose;
1893 for (; hp != NULL; hp = hp->younger) {
1894 if (!hp->isgabby || dogabby) {
1895 if (hp->isgabby)
1896 putc('*', f);
1897 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1898 putc('\n', f);
1901 jclose:
1902 fclose(f);
1903 jdone:
1904 rele_all_sigs(); /* XXX remove jumps */
1905 jleave:
1906 # endif /* HAVE_HISTORY */
1907 NYD_LEAVE;
1910 FL void
1911 tty_signal(int sig)
1913 sigset_t nset, oset;
1914 NYD_X; /* Signal handler */
1916 switch (sig) {
1917 case SIGWINCH:
1918 /* We don't deal with SIGWINCH, yet get called from main.c */
1919 break;
1920 default:
1921 _ncl_term_mode(FAL0);
1922 _ncl_sigs_down();
1923 sigemptyset(&nset);
1924 sigaddset(&nset, sig);
1925 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1926 kill(0, sig);
1927 /* When we come here we'll continue editing, so reestablish */
1928 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1929 _ncl_sigs_up();
1930 _ncl_term_mode(TRU1);
1931 break;
1935 FL int
1936 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1937 SMALLOC_DEBUG_ARGS)
1939 ssize_t nn;
1940 NYD_ENTER;
1942 /* Of course we have races here, but they cannot be avoided on POSIX
1943 * (except by even *more* actions) */
1944 _ncl_sigs_up();
1945 _ncl_term_mode(TRU1);
1946 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1947 _ncl_term_mode(FAL0);
1948 _ncl_sigs_down();
1949 NYD_LEAVE;
1950 return (int)nn;
1953 FL void
1954 tty_addhist(char const *s, bool_t isgabby)
1956 # ifdef HAVE_HISTORY
1957 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1958 ui32_t l;
1959 struct hist *h, *o, *y;
1960 # endif
1961 NYD_ENTER;
1962 UNUSED(s);
1963 UNUSED(isgabby);
1965 # ifdef HAVE_HISTORY
1966 if (isgabby && !ok_blook(history_gabby))
1967 goto j_leave;
1968 if (_ncl_hist_size_max == 0)
1969 goto j_leave;
1970 _CL_CHECK_ADDHIST(s, goto j_leave);
1972 l = (ui32_t)strlen(s);
1974 /* Eliminating duplicates is expensive, but simply inacceptable so
1975 * during the load of a potentially large history file! */
1976 if (!_ncl_hist_load)
1977 for (h = _ncl_hist; h != NULL; h = h->older)
1978 if (h->len == l && !strcmp(h->dat, s)) {
1979 hold_all_sigs(); /* TODO */
1980 if (h->isgabby)
1981 h->isgabby = !!isgabby;
1982 o = h->older;
1983 y = h->younger;
1984 if (o != NULL)
1985 o->younger = y;
1986 else
1987 _ncl_hist_tail = y;
1988 if (y != NULL)
1989 y->older = o;
1990 else
1991 _ncl_hist = o;
1992 goto jleave;
1994 hold_all_sigs();
1996 ++_ncl_hist_size;
1997 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1998 --_ncl_hist_size;
1999 if ((h = _ncl_hist_tail) != NULL) {
2000 if ((_ncl_hist_tail = h->younger) == NULL)
2001 _ncl_hist = NULL;
2002 else
2003 _ncl_hist_tail->older = NULL;
2004 free(h);
2008 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
2009 h->isgabby = !!isgabby;
2010 h->len = l;
2011 memcpy(h->dat, s, l +1);
2012 jleave:
2013 if ((h->older = _ncl_hist) != NULL)
2014 _ncl_hist->younger = h;
2015 else
2016 _ncl_hist_tail = h;
2017 h->younger = NULL;
2018 _ncl_hist = h;
2020 rele_all_sigs();
2021 j_leave:
2022 # endif
2023 NYD_LEAVE;
2026 # ifdef HAVE_HISTORY
2027 FL int
2028 c_history(void *v)
2030 C_HISTORY_SHARED;
2032 jlist: {
2033 FILE *fp;
2034 size_t i, b;
2035 struct hist *h;
2037 if (_ncl_hist == NULL)
2038 goto jleave;
2040 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
2041 NULL) {
2042 perror("tmpfile");
2043 v = NULL;
2044 goto jleave;
2047 i = _ncl_hist_size;
2048 b = 0;
2049 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
2050 fprintf(fp, "%c%4" ZFMT ". %-50.50s (%4" ZFMT "+%2u bytes)\n",
2051 (h->isgabby ? '*' : ' '), i, h->dat, b, h->len);
2053 page_or_print(fp, i);
2054 Fclose(fp);
2056 goto jleave;
2058 jclear: {
2059 struct hist *h;
2060 while ((h = _ncl_hist) != NULL) {
2061 _ncl_hist = h->older;
2062 free(h);
2064 _ncl_hist_tail = NULL;
2065 _ncl_hist_size = 0;
2067 goto jleave;
2069 jentry: {
2070 struct hist *h = _ncl_hist;
2071 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2072 entry = (long)_ncl_hist_size - entry;
2073 for (h = _ncl_hist;; h = h->older)
2074 if (h == NULL)
2075 break;
2076 else if (entry-- != 0)
2077 continue;
2078 else {
2079 v = temporary_arg_v_store = h->dat;
2080 goto jleave;
2083 v = NULL;
2085 goto jleave;
2087 # endif /* HAVE_HISTORY */
2088 #endif /* HAVE_NCL */
2091 * The really-nothing-at-all implementation
2094 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2095 FL void
2096 tty_init(void)
2098 NYD_ENTER;
2099 NYD_LEAVE;
2102 FL void
2103 tty_destroy(void)
2105 NYD_ENTER;
2106 NYD_LEAVE;
2109 FL void
2110 tty_signal(int sig)
2112 NYD_X; /* Signal handler */
2113 UNUSED(sig);
2116 FL int
2117 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2118 SMALLOC_DEBUG_ARGS)
2120 /* TODO The nothing-at-all tty layer even forces re-entering all the
2121 * TODO original data when re-editing a field */
2122 bool_t doffl = FAL0;
2123 int rv;
2124 NYD_ENTER;
2126 if (prompt != NULL && *prompt != '\0') {
2127 fputs(prompt, stdout);
2128 doffl = TRU1;
2130 if (n > 0) {
2131 fprintf(stdout, _("{former content: %.*s} "), (int)n, *linebuf);
2132 n = 0;
2133 doffl = TRU1;
2135 if (doffl)
2136 fflush(stdout);
2137 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2138 NYD_LEAVE;
2139 return rv;
2142 FL void
2143 tty_addhist(char const *s, bool_t isgabby)
2145 NYD_ENTER;
2146 UNUSED(s);
2147 UNUSED(isgabby);
2148 NYD_LEAVE;
2150 #endif /* nothing at all */
2152 /* s-it-mode */