TODO: howto overcome our signal no-go dead-end situation
[s-mailx.git] / tty.c
blob5957a59ab657f011a7850e6eee9e05c42f21c896
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", tr(431,\
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 *prompt, bool_t noninteract_default)
145 bool_t rv;
146 NYD_ENTER;
148 if (!(options & OPT_INTERACTIVE)) {
149 rv = noninteract_default;
150 goto jleave;
153 if (prompt == NULL)
154 prompt = tr(264, "Continue (y/n)? ");
156 rv = FAL0;
157 if (readline_input(prompt, FAL0, &termios_state.ts_linebuf,
158 &termios_state.ts_linesize, NULL) >= 0)
159 switch (termios_state.ts_linebuf[0]) {
160 case 'y':
161 case 'Y':
162 rv = TRU1;
163 /* FALLTHRU */
164 default:
165 break;
167 termios_state_reset();
168 jleave:
169 NYD_LEAVE;
170 return rv;
173 FL bool_t
174 yorn(char const *msg) /* TODO obsolete */
176 bool_t rv;
177 NYD_ENTER;
179 rv = getapproval(msg, TRU1);
180 NYD_LEAVE;
181 return rv;
184 FL char *
185 getuser(char const * volatile query)
187 sighandler_type volatile ohdl;
188 char *user = NULL;
189 bool_t hadsig = FAL0;
190 NYD_ENTER;
192 if (query == NULL)
193 query = tr(509, "User: ");
195 ohdl = safe_signal(SIGINT, SIG_IGN);
196 if (sigsetjmp(__tty_actjmp, 1) != 0) {
197 hadsig = TRU1;
198 goto jrestore;
200 safe_signal(SIGINT, &__tty_acthdl);
202 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
203 &termios_state.ts_linesize, NULL) >= 0)
204 user = termios_state.ts_linebuf;
205 jrestore:
206 termios_state_reset();
207 safe_signal(SIGINT, ohdl);
208 NYD_LEAVE;
209 if (hadsig && ohdl != SIG_IGN)
210 kill(0, SIGINT);
211 return user;
214 FL char *
215 getpassword(char const *query) /* FIXME encaps ttystate signal safe */
217 sighandler_type volatile ohdl;
218 struct termios tios;
219 char *pass = NULL;
220 bool_t hadsig = FAL0;
221 NYD_ENTER;
223 if (query == NULL)
224 query = tr(510, "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 hadsig = TRU1;
243 goto jrestore;
245 safe_signal(SIGINT, &__tty_acthdl);
247 if (readline_restart(stdin, &termios_state.ts_linebuf,
248 &termios_state.ts_linesize, 0) >= 0)
249 pass = termios_state.ts_linebuf;
250 jrestore:
251 termios_state_reset();
252 safe_signal(SIGINT, ohdl);
253 if (options & OPT_TTYIN)
254 fputc('\n', stdout);
255 NYD_LEAVE;
256 if (hadsig && ohdl != SIG_IGN)
257 kill(0, SIGINT);
258 return pass;
261 FL bool_t
262 getcredentials(char **user, char **pass)
264 bool_t rv;
265 char *u, *p;
266 NYD_ENTER;
268 rv = TRU1;
269 u = *user;
270 p = *pass;
272 if (u == NULL) {
273 if ((u = getuser(NULL)) == NULL)
274 rv = FAL0;
275 else if (p == NULL)
276 u = savestr(u);
277 *user = u;
280 if (p == NULL) {
281 if ((p = getpassword(NULL)) == NULL)
282 rv = FAL0;
283 *pass = p;
285 NYD_LEAVE;
286 return rv;
290 * readline(3)
293 #ifdef HAVE_READLINE
294 static sighandler_type _rl_shup;
295 static char * _rl_buf; /* pre_input() hook: initial line */
296 static int _rl_buflen; /* content, and its length */
298 static int _rl_pre_input(void);
300 static int
301 _rl_pre_input(void)
303 NYD_ENTER;
304 /* Handle leftover data from \ escaped former line */
305 rl_extend_line_buffer(_rl_buflen + 10);
306 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
307 rl_point = rl_end = _rl_buflen;
308 rl_pre_input_hook = (rl_hook_func_t*)NULL;
309 rl_redisplay();
310 NYD_LEAVE;
311 return 0;
314 FL void
315 tty_init(void)
317 # ifdef HAVE_HISTORY
318 long hs;
319 char *v;
320 # endif
321 NYD_ENTER;
323 rl_readline_name = UNCONST(uagent);
324 # ifdef HAVE_HISTORY
325 _CL_HISTSIZE(hs);
326 using_history();
327 stifle_history((int)hs);
328 # endif
329 rl_read_init_file(NULL);
331 /* Because rl_read_init_file() may have introduced yet a different
332 * history size limit, simply load and incorporate the history, leave
333 * it up to readline(3) to do the rest */
334 # ifdef HAVE_HISTORY
335 _CL_HISTFILE(v);
336 if (v != NULL)
337 read_history(v);
338 # endif
339 NYD_LEAVE;
342 FL void
343 tty_destroy(void)
345 # ifdef HAVE_HISTORY
346 char *v;
347 # endif
348 NYD_ENTER;
350 # ifdef HAVE_HISTORY
351 _CL_HISTFILE(v);
352 if (v != NULL)
353 write_history(v);
354 # endif
355 NYD_LEAVE;
358 FL void
359 tty_signal(int sig)
361 sigset_t nset, oset;
362 NYD_X; /* Signal handler */
364 switch (sig) {
365 # ifdef SIGWINCH
366 case SIGWINCH:
367 break;
368 # endif
369 case SIGHUP:
370 /* readline(3) doesn't catch it :( */
371 rl_free_line_state();
372 rl_cleanup_after_signal();
373 safe_signal(SIGHUP, _rl_shup);
374 sigemptyset(&nset);
375 sigaddset(&nset, sig);
376 sigprocmask(SIG_UNBLOCK, &nset, &oset);
377 kill(0, sig);
378 /* XXX When we come here we'll continue editing, so reestablish
379 * XXX cannot happen */
380 sigprocmask(SIG_BLOCK, &oset, NULL);
381 _rl_shup = safe_signal(SIGHUP, &tty_signal);
382 rl_reset_after_signal();
383 break;
384 default:
385 break;
389 FL int
390 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
391 SMALLOC_DEBUG_ARGS)
393 int nn;
394 char *line;
395 NYD_ENTER;
397 if (n > 0) {
398 _rl_buf = *linebuf;
399 _rl_buflen = (int)n;
400 rl_pre_input_hook = &_rl_pre_input;
403 _rl_shup = safe_signal(SIGHUP, &tty_signal);
404 line = readline(prompt != NULL ? prompt : "");
405 safe_signal(SIGHUP, _rl_shup);
407 if (line == NULL) {
408 nn = -1;
409 goto jleave;
411 n = strlen(line);
413 if (n >= *linesize) {
414 *linesize = LINESIZE + n +1;
415 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
417 memcpy(*linebuf, line, n);
418 (free)(line);
419 (*linebuf)[n] = '\0';
420 nn = (int)n;
421 jleave:
422 NYD_LEAVE;
423 return nn;
426 FL void
427 tty_addhist(char const *s)
429 NYD_ENTER;
430 UNUSED(s);
431 # ifdef HAVE_HISTORY
432 _CL_CHECK_ADDHIST(s, goto jleave);
433 hold_all_sigs(); /* XXX too heavy */
434 add_history(s); /* XXX yet we jump away! */
435 rele_all_sigs(); /* XXX remove jumps */
436 jleave:
437 # endif
438 NYD_LEAVE;
441 # ifdef HAVE_HISTORY
442 FL int
443 c_history(void *v)
445 C_HISTORY_SHARED;
447 jlist: {
448 FILE *fp;
449 HISTORY_STATE *hs;
450 HIST_ENTRY **hl;
451 ul_it i, b;
453 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
454 NULL) {
455 perror("tmpfile");
456 v = NULL;
457 goto jleave;
460 hs = history_get_history_state();
462 for (i = (ul_it)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
463 char *cp = (*--hl)->line;
464 size_t sl = strlen(cp);
465 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n", i, cp, b, sl);
466 b += sl;
469 page_or_print(fp, (size_t)hs->length);
470 Fclose(fp);
472 goto jleave;
474 jclear:
475 clear_history();
476 goto jleave;
478 jentry: {
479 HISTORY_STATE *hs = history_get_history_state();
481 if (UICMP(z, entry, <=, hs->length))
482 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
483 else
484 v = NULL;
486 goto jleave;
488 # endif /* HAVE_HISTORY */
489 #endif /* HAVE_READLINE */
492 * BSD editline(3)
495 #ifdef HAVE_EDITLINE
496 static EditLine * _el_el; /* editline(3) handle */
497 static char const * _el_prompt; /* Current prompt */
498 # ifdef HAVE_HISTORY
499 static History * _el_hcom; /* History handle for commline */
500 # endif
502 static char const * _el_getprompt(void);
504 static char const *
505 _el_getprompt(void)
507 return _el_prompt;
510 FL void
511 tty_init(void)
513 # ifdef HAVE_HISTORY
514 HistEvent he;
515 long hs;
516 char *v;
517 # endif
518 NYD_ENTER;
520 # ifdef HAVE_HISTORY
521 _CL_HISTSIZE(hs);
522 _el_hcom = history_init();
523 history(_el_hcom, &he, H_SETSIZE, (int)hs);
524 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
525 # endif
527 _el_el = el_init(uagent, stdin, stdout, stderr);
528 el_set(_el_el, EL_SIGNAL, 1);
529 el_set(_el_el, EL_TERMINAL, NULL);
530 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
531 # ifdef HAVE_HISTORY
532 el_set(_el_el, EL_HIST, &history, _el_hcom);
533 # endif
534 el_set(_el_el, EL_EDITOR, "emacs");
535 # ifdef EL_PROMPT_ESC
536 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
537 # else
538 el_set(_el_el, EL_PROMPT, &_el_getprompt);
539 # endif
540 # if 0
541 el_set(_el_el, EL_ADDFN, "tab_complete",
542 "editline(3) internal completion function", &_el_file_cpl);
543 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
544 # endif
545 # ifdef HAVE_HISTORY
546 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
547 # endif
548 el_source(_el_el, NULL); /* Source ~/.editrc */
550 /* Because el_source() may have introduced yet a different history size
551 * limit, simply load and incorporate the history, leave it up to
552 * editline(3) to do the rest */
553 # ifdef HAVE_HISTORY
554 _CL_HISTFILE(v);
555 if (v != NULL)
556 history(_el_hcom, &he, H_LOAD, v);
557 # endif
558 NYD_LEAVE;
561 FL void
562 tty_destroy(void)
564 # ifdef HAVE_HISTORY
565 HistEvent he;
566 char *v;
567 # endif
568 NYD_ENTER;
570 el_end(_el_el);
572 # ifdef HAVE_HISTORY
573 _CL_HISTFILE(v);
574 if (v != NULL)
575 history(_el_hcom, &he, H_SAVE, v);
576 history_end(_el_hcom);
577 # endif
578 NYD_LEAVE;
581 FL void
582 tty_signal(int sig)
584 NYD_X; /* Signal handler */
585 switch (sig) {
586 # ifdef SIGWINCH
587 case SIGWINCH:
588 el_resize(_el_el);
589 break;
590 # endif
591 default:
592 break;
596 FL int
597 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
598 SMALLOC_DEBUG_ARGS)
600 int nn;
601 char const *line;
602 NYD_ENTER;
604 _el_prompt = (prompt != NULL) ? prompt : "";
605 if (n > 0)
606 el_push(_el_el, *linebuf);
607 line = el_gets(_el_el, &nn);
609 if (line == NULL) {
610 nn = -1;
611 goto jleave;
613 assert(nn >= 0);
614 n = (size_t)nn;
615 if (n > 0 && line[n - 1] == '\n')
616 nn = (int)--n;
618 if (n >= *linesize) {
619 *linesize = LINESIZE + n + 1;
620 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
622 memcpy(*linebuf, line, n);
623 (*linebuf)[n] = '\0';
624 jleave:
625 NYD_LEAVE;
626 return nn;
629 FL void
630 tty_addhist(char const *s)
632 # ifdef HAVE_HISTORY
633 /* Enlarge meaning of unique .. to something that rocks;
634 * xxx unfortunately this is expensive to do with editline(3)
635 * xxx maybe it would be better to hook the ptfs instead? */
636 HistEvent he;
637 int i;
638 # endif
639 NYD_ENTER;
640 UNUSED(s);
642 # ifdef HAVE_HISTORY
643 _CL_CHECK_ADDHIST(s, goto jleave);
645 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
646 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
647 i = history(_el_hcom, &he, H_NEXT))
648 if (!strcmp(he.str, s)) {
649 history(_el_hcom, &he, H_DEL, he.num);
650 break;
652 history(_el_hcom, &he, H_ENTER, s);
653 rele_all_sigs(); /* XXX remove jumps */
654 jleave:
655 # endif
656 NYD_LEAVE;
659 # ifdef HAVE_HISTORY
660 FL int
661 c_history(void *v)
663 C_HISTORY_SHARED;
665 jlist: {
666 HistEvent he;
667 FILE *fp;
668 size_t i, b;
669 int x;
671 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
672 NULL) {
673 perror("tmpfile");
674 v = NULL;
675 goto jleave;
678 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
679 b = 0;
680 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
681 x = history(_el_hcom, &he, H_NEXT)) {
682 size_t sl = strlen(he.str);
683 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
684 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
685 --i;
686 b += sl;
689 page_or_print(fp, i);
690 Fclose(fp);
692 goto jleave;
694 jclear: {
695 HistEvent he;
696 history(_el_hcom, &he, H_CLEAR);
698 goto jleave;
700 jentry: {
701 HistEvent he;
702 size_t i;
703 int x;
705 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
706 if (UICMP(z, entry, <=, i)) {
707 entry = (long)i - entry;
708 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
709 x = history(_el_hcom, &he, H_NEXT))
710 if (entry-- == 0) {
711 v = temporary_arg_v_store = UNCONST(he.str);
712 goto jleave;
715 v = NULL;
717 goto jleave;
719 # endif /* HAVE_HISTORY */
720 #endif /* HAVE_EDITLINE */
723 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
725 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
726 * We do not handle character widths because the terminal must deal with that
727 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
728 * characters by definition on the other. We're addicted.
730 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
731 * we're forced to use the very same buffer--the one that is passed through to
732 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
733 * convert that on-the-fly back to the plain char* result once we're done.
734 * To simplify our live, use savestr() buffers for all other needed memory
738 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
739 * TODO NCL: during handler de-/installation handling.
742 #ifdef HAVE_NCL
743 # ifndef MAX_INPUT
744 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
745 # endif
747 /* Since we simply fputs(3) the prompt, assume each character requires two
748 * visual cells -- and we need to restrict the maximum prompt size because
749 * of MAX_INPUT and our desire to have room for some error message left */
750 # define _PROMPT_VLEN(P) (strlen(P) * 2)
751 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
753 union xsighdl {
754 sighandler_type shdl; /* Try avoid races by setting */
755 sl_it sint; /* .sint=-1 when inactive */
757 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
759 struct xtios {
760 struct termios told;
761 struct termios tnew;
764 struct cell {
765 wchar_t wc;
766 ui32_t count;
767 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
770 struct line {
771 size_t cursor; /* Current cursor position */
772 size_t topins; /* Outermost cursor col set */
773 union {
774 char * cbuf; /* *x_buf */
775 struct cell * cells;
776 } line;
777 struct str defc; /* Current default content */
778 struct str savec; /* Saved default content */
779 # ifdef HAVE_HISTORY
780 struct hist * hist; /* History cursor */
781 # endif
782 char const * prompt;
783 char const * nd; /* Cursor right */
784 char ** x_buf; /* Caller pointers */
785 size_t * x_bufsize;
788 # ifdef HAVE_HISTORY
789 struct hist {
790 struct hist * older;
791 struct hist * younger;
792 size_t len;
793 char dat[VFIELD_SIZE(sizeof(size_t))];
795 # endif
797 static union xsighdl _ncl_oint;
798 static union xsighdl _ncl_oquit;
799 static union xsighdl _ncl_oterm;
800 static union xsighdl _ncl_ohup;
801 static union xsighdl _ncl_otstp;
802 static union xsighdl _ncl_ottin;
803 static union xsighdl _ncl_ottou;
804 static struct xtios _ncl_tios;
805 # ifdef HAVE_HISTORY
806 static struct hist * _ncl_hist;
807 static struct hist * _ncl_hist_tail;
808 static size_t _ncl_hist_size;
809 static size_t _ncl_hist_size_max;
810 static bool_t _ncl_hist_load;
811 # endif
813 static void _ncl_sigs_up(void);
814 static void _ncl_sigs_down(void);
816 static void _ncl_term_mode(bool_t raw);
818 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
819 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
820 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
821 static ssize_t _ncl_cell2dat(struct line *l);
822 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
823 static void _ncl_cell2save(struct line *l);
824 # endif
826 static void _ncl_khome(struct line *l, bool_t dobell);
827 static void _ncl_kend(struct line *l);
828 static void _ncl_kbs(struct line *l);
829 static void _ncl_kkill(struct line *l, bool_t dobell);
830 static ssize_t _ncl_keof(struct line *l);
831 static void _ncl_kleft(struct line *l);
832 static void _ncl_kright(struct line *l);
833 static void _ncl_krefresh(struct line *l);
834 static void _ncl_kbwddelw(struct line *l);
835 static void _ncl_kgow(struct line *l, ssize_t dir);
836 static void _ncl_kother(struct line *l, wchar_t wc);
837 # ifdef HAVE_HISTORY
838 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
839 static size_t _ncl_khist(struct line *l, bool_t backwd);
840 static size_t _ncl_krhist(struct line *l);
841 # endif
842 # ifdef HAVE_TABEXPAND
843 static size_t _ncl_kht(struct line *l);
844 # endif
845 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
846 size_t len SMALLOC_DEBUG_ARGS);
848 static void
849 _ncl_sigs_up(void)
851 NYD_ENTER;
852 if (_ncl_oint.sint == -1)
853 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
854 if (_ncl_oquit.sint == -1)
855 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
856 if (_ncl_oterm.sint == -1)
857 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
858 if (_ncl_ohup.sint == -1)
859 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
860 if (_ncl_otstp.sint == -1)
861 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
862 if (_ncl_ottin.sint == -1)
863 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
864 if (_ncl_ottou.sint == -1)
865 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
866 NYD_LEAVE;
869 static void
870 _ncl_sigs_down(void)
872 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
873 sighandler_type st;
874 NYD_ENTER;
876 if (_ncl_ottou.sint != -1) {
877 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
878 safe_signal(SIGTTOU, st);
880 if (_ncl_ottin.sint != -1) {
881 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
882 safe_signal(SIGTTIN, st);
884 if (_ncl_otstp.sint != -1) {
885 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
886 safe_signal(SIGTSTP, st);
888 if (_ncl_ohup.sint != -1) {
889 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
890 safe_signal(SIGHUP, st);
892 if (_ncl_oterm.sint != -1) {
893 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
894 safe_signal(SIGTERM, st);
896 if (_ncl_oquit.sint != -1) {
897 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
898 safe_signal(SIGQUIT, st);
900 if (_ncl_oint.sint != -1) {
901 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
902 safe_signal(SIGINT, st);
904 NYD_LEAVE;
907 static void
908 _ncl_term_mode(bool_t raw)
910 struct termios *tiosp;
911 NYD_ENTER;
913 tiosp = &_ncl_tios.told;
914 if (!raw)
915 goto jleave;
917 /* Always requery the attributes, in case we've been moved from background
918 * to foreground or however else in between sessions */
919 tcgetattr(STDIN_FILENO, tiosp);
920 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
921 tiosp = &_ncl_tios.tnew;
922 tiosp->c_cc[VMIN] = 1;
923 tiosp->c_cc[VTIME] = 0;
924 tiosp->c_iflag &= ~(ISTRIP);
925 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
926 jleave:
927 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
928 NYD_LEAVE;
931 static void
932 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
934 size_t i;
935 NYD_ENTER;
937 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
938 if (i > *l->x_bufsize) {
939 i <<= 1;
940 *l->x_bufsize = i;
941 l->line.cbuf =
942 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
944 NYD_LEAVE;
947 static void
948 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
950 size_t j;
951 NYD_ENTER;
953 if (i > 0)
954 memmove(cap, cap + 1, i * sizeof(*cap));
956 /* And.. the (rest of the) visual update */
957 for (j = 0; j < i; ++j)
958 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
959 fputs(" \b", stdout);
960 for (j = 0; j < i; ++j)
961 putchar('\b');
962 NYD_LEAVE;
965 static ssize_t
966 _ncl_wboundary(struct line *l, ssize_t dir)
968 size_t c, t, i;
969 struct cell *cap;
970 bool_t anynon;
971 NYD_ENTER;
973 c = l->cursor;
974 t = l->topins;
975 i = (size_t)-1;
977 if (dir < 0) {
978 if (c == 0)
979 goto jleave;
980 } else if (c == t)
981 goto jleave;
982 else
983 --t, --c; /* Unsigned wrapping may occur (twice), then */
985 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
986 wchar_t wc = cap[c + dir].wc;
987 if (iswblank(wc) || iswpunct(wc)) {
988 if (anynon)
989 break;
990 } else
991 anynon = TRU1;
992 ++i;
993 c += dir;
994 if (dir < 0) {
995 if (c == 0)
996 break;
997 } else if (c == t)
998 break;
1000 jleave:
1001 NYD_LEAVE;
1002 return (ssize_t)i;
1005 static ssize_t
1006 _ncl_cell2dat(struct line *l)
1008 size_t len = 0, i;
1009 NYD_ENTER;
1011 if (l->topins > 0)
1012 for (i = 0; i < l->topins; ++i) {
1013 struct cell *cap = l->line.cells + i;
1014 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
1015 len += cap->count;
1017 l->line.cbuf[len] = '\0';
1018 NYD_LEAVE;
1019 return (ssize_t)len;
1022 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
1023 static void
1024 _ncl_cell2save(struct line *l)
1026 size_t len, i;
1027 struct cell *cap;
1028 NYD_ENTER;
1030 l->savec.s = NULL, l->savec.l = 0;
1031 if (l->topins == 0)
1032 goto jleave;
1034 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
1035 len += cap->count;
1037 l->savec.l = len;
1038 l->savec.s = salloc(len + 1);
1040 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
1041 memcpy(l->savec.s + len, cap->cbuf, cap->count);
1042 len += cap->count;
1044 l->savec.s[len] = '\0';
1045 jleave:
1046 NYD_LEAVE;
1048 # endif
1050 static void
1051 _ncl_khome(struct line *l, bool_t dobell)
1053 size_t c;
1054 NYD_ENTER;
1056 c = l->cursor;
1057 if (c > 0) {
1058 l->cursor = 0;
1059 while (c-- != 0)
1060 putchar('\b');
1061 } else if (dobell)
1062 putchar('\a');
1063 NYD_LEAVE;
1066 static void
1067 _ncl_kend(struct line *l)
1069 ssize_t i;
1070 NYD_ENTER;
1072 i = (ssize_t)(l->topins - l->cursor);
1074 if (i > 0) {
1075 l->cursor = l->topins;
1076 while (i-- != 0)
1077 fputs(l->nd, stdout);
1078 } else
1079 putchar('\a');
1080 NYD_LEAVE;
1083 static void
1084 _ncl_kbs(struct line *l)
1086 ssize_t c, t;
1087 NYD_ENTER;
1089 c = l->cursor;
1090 t = l->topins;
1092 if (c > 0) {
1093 putchar('\b');
1094 l->cursor = --c;
1095 l->topins = --t;
1096 t -= c;
1097 _ncl_bs_eof_dvup(l->line.cells + c, t);
1098 } else
1099 putchar('\a');
1100 NYD_LEAVE;
1103 static void
1104 _ncl_kkill(struct line *l, bool_t dobell)
1106 size_t j, c, i;
1107 NYD_ENTER;
1109 c = l->cursor;
1110 i = (size_t)(l->topins - c);
1112 if (i > 0) {
1113 l->topins = c;
1114 for (j = i; j != 0; --j)
1115 putchar(' ');
1116 for (j = i; j != 0; --j)
1117 putchar('\b');
1118 } else if (dobell)
1119 putchar('\a');
1120 NYD_LEAVE;
1123 static ssize_t
1124 _ncl_keof(struct line *l)
1126 size_t c, t;
1127 ssize_t i;
1128 NYD_ENTER;
1130 c = l->cursor;
1131 t = l->topins;
1132 i = (ssize_t)(t - c);
1134 if (i > 0) {
1135 l->topins = --t;
1136 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1137 } else if (t == 0 && !ok_blook(ignoreeof)) {
1138 fputs("^D", stdout);
1139 fflush(stdout);
1140 i = -1;
1141 } else {
1142 putchar('\a');
1143 i = 0;
1145 NYD_LEAVE;
1146 return i;
1149 static void
1150 _ncl_kleft(struct line *l)
1152 NYD_ENTER;
1153 if (l->cursor > 0) {
1154 --l->cursor;
1155 putchar('\b');
1156 } else
1157 putchar('\a');
1158 NYD_LEAVE;
1161 static void
1162 _ncl_kright(struct line *l)
1164 NYD_ENTER;
1165 if (l->cursor < l->topins) {
1166 ++l->cursor;
1167 fputs(l->nd, stdout);
1168 } else
1169 putchar('\a');
1170 NYD_LEAVE;
1173 static void
1174 _ncl_krefresh(struct line *l)
1176 struct cell *cap;
1177 size_t i;
1178 NYD_ENTER;
1180 putchar('\r');
1181 if (l->prompt != NULL && *l->prompt != '\0')
1182 fputs(l->prompt, stdout);
1183 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1184 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1185 for (i = l->topins - l->cursor; i > 0; --i)
1186 putchar('\b');
1187 NYD_LEAVE;
1190 static void
1191 _ncl_kbwddelw(struct line *l)
1193 ssize_t i;
1194 size_t c, t, j;
1195 struct cell *cap;
1196 NYD_ENTER;
1198 i = _ncl_wboundary(l, -1);
1199 if (i <= 0) {
1200 if (i < 0)
1201 putchar('\a');
1202 goto jleave;
1205 c = l->cursor - i;
1206 t = l->topins;
1207 l->topins = t - i;
1208 l->cursor = c;
1209 cap = l->line.cells + c;
1211 if (t != l->cursor) {
1212 j = t - c + i;
1213 memmove(cap, cap + i, j * sizeof(*cap));
1216 for (j = i; j > 0; --j)
1217 putchar('\b');
1218 for (j = l->topins - c; j > 0; ++cap, --j)
1219 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1220 for (j = i; j > 0; --j)
1221 putchar(' ');
1222 for (j = t - c; j > 0; --j)
1223 putchar('\b');
1224 jleave:
1225 NYD_LEAVE;
1228 static void
1229 _ncl_kgow(struct line *l, ssize_t dir)
1231 ssize_t i;
1232 NYD_ENTER;
1234 i = _ncl_wboundary(l, dir);
1235 if (i <= 0) {
1236 if (i < 0)
1237 putchar('\a');
1238 goto jleave;
1241 if (dir < 0) {
1242 l->cursor -= i;
1243 while (i-- > 0)
1244 putchar('\b');
1245 } else {
1246 l->cursor += i;
1247 while (i-- > 0)
1248 fputs(l->nd, stdout);
1250 jleave:
1251 NYD_LEAVE;
1254 static void
1255 _ncl_kother(struct line *l, wchar_t wc)
1257 /* Append if at EOL, insert otherwise;
1258 * since we may move around character-wise, always use a fresh ps */
1259 mbstate_t ps;
1260 struct cell cell, *cap;
1261 size_t i, c;
1262 NYD_ENTER;
1264 /* First init a cell and see wether we'll really handle this wc */
1265 cell.wc = wc;
1266 memset(&ps, 0, sizeof ps);
1267 i = wcrtomb(cell.cbuf, wc, &ps);
1268 if (i > MB_LEN_MAX)
1269 goto jleave;
1270 cell.count = (ui_it)i;
1271 if (enc_has_state) {
1272 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1273 if (i == 1)
1275 else if (--i < MB_LEN_MAX)
1276 cell.count += (ui_it)i;
1277 else
1278 goto jleave;
1281 /* Yes, we will! Place it in the array */
1282 c = l->cursor++;
1283 i = l->topins++ - c;
1284 cap = l->line.cells + c;
1285 if (i > 0)
1286 memmove(cap + 1, cap, i * sizeof(cell));
1287 memcpy(cap, &cell, sizeof cell);
1289 /* And update visual */
1290 c = i;
1292 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1293 while ((++cap, i-- != 0));
1294 while (c-- != 0)
1295 putchar('\b');
1296 jleave:
1297 NYD_LEAVE;
1300 # ifdef HAVE_HISTORY
1301 static size_t
1302 __ncl_khist_shared(struct line *l, struct hist *hp)
1304 size_t rv;
1305 NYD_ENTER;
1307 if ((l->hist = hp) != NULL) {
1308 l->defc.s = savestrbuf(hp->dat, hp->len);
1309 rv =
1310 l->defc.l = hp->len;
1311 if (l->topins > 0) {
1312 _ncl_khome(l, FAL0);
1313 _ncl_kkill(l, FAL0);
1315 } else {
1316 putchar('\a');
1317 rv = 0;
1319 NYD_LEAVE;
1320 return rv;
1323 static size_t
1324 _ncl_khist(struct line *l, bool_t backwd)
1326 struct hist *hp;
1327 size_t rv;
1328 NYD_ENTER;
1330 /* If we're not in history mode yet, save line content;
1331 * also, disallow forward search, then, and, of course, bail unless we
1332 * do have any history at all */
1333 if ((hp = l->hist) == NULL) {
1334 if (!backwd)
1335 goto jleave;
1336 if ((hp = _ncl_hist) == NULL)
1337 goto jleave;
1338 _ncl_cell2save(l);
1339 goto jleave;
1342 hp = backwd ? hp->older : hp->younger;
1343 jleave:
1344 rv = __ncl_khist_shared(l, hp);
1345 NYD_LEAVE;
1346 return rv;
1349 static size_t
1350 _ncl_krhist(struct line *l)
1352 struct str orig_savec;
1353 struct hist *hp = NULL;
1354 size_t rv;
1355 NYD_ENTER;
1357 /* We cannot complete an empty line */
1358 if (l->topins == 0) {
1359 /* XXX The upcoming hard reset would restore a set savec buffer,
1360 * XXX so forcefully reset that. A cleaner solution would be to
1361 * XXX reset it whenever a restore is no longer desired */
1362 l->savec.s = NULL, l->savec.l = 0;
1363 goto jleave;
1365 if ((hp = l->hist) == NULL) {
1366 if ((hp = _ncl_hist) == NULL)
1367 goto jleave;
1368 orig_savec.s = NULL;
1369 orig_savec.l = 0; /* silence CC */
1370 } else if ((hp = hp->older) == NULL)
1371 goto jleave;
1372 else
1373 orig_savec = l->savec;
1375 if (orig_savec.s == NULL)
1376 _ncl_cell2save(l);
1377 for (; hp != NULL; hp = hp->older)
1378 if (is_prefix(l->savec.s, hp->dat))
1379 break;
1380 if (orig_savec.s != NULL)
1381 l->savec = orig_savec;
1382 jleave:
1383 rv = __ncl_khist_shared(l, hp);
1384 NYD_LEAVE;
1385 return rv;
1387 # endif
1389 # ifdef HAVE_TABEXPAND
1390 static size_t
1391 _ncl_kht(struct line *l)
1393 struct str orig, bot, topp, sub, exp;
1394 struct cell *cword, *ctop, *cx;
1395 bool_t set_savec = FAL0;
1396 size_t rv = 0;
1397 NYD_ENTER;
1399 /* We cannot expand an empty line */
1400 if (l->topins == 0)
1401 goto jleave;
1403 /* Get plain line data; if this is the first expansion/xy, update the
1404 * very original content so that ^G gets the origin back */
1405 orig = l->savec;
1406 _ncl_cell2save(l);
1407 exp = l->savec;
1408 if (orig.s != NULL)
1409 l->savec = orig;
1410 else
1411 set_savec = TRU1;
1412 orig = exp;
1414 cword = l->line.cells;
1415 ctop = cword + l->cursor;
1417 /* topp: separate data right of cursor */
1418 if ((cx = cword + l->topins) != ctop) {
1419 for (rv = 0; cx > ctop; --cx)
1420 rv += cx->count;
1421 topp.l = rv;
1422 topp.s = orig.s + orig.l - rv;
1423 } else
1424 topp.s = NULL, topp.l = 0;
1426 /* bot, sub: we cannot expand the entire data left of cursor, but only
1427 * the last "word", so separate them */
1428 while (cx > cword && !iswspace(cx[-1].wc))
1429 --cx;
1430 for (rv = 0; cword < cx; ++cword)
1431 rv += cword->count;
1432 sub =
1433 bot = orig;
1434 bot.l = rv;
1435 sub.s += rv;
1436 sub.l -= rv;
1437 sub.l -= topp.l;
1439 /* Leave room for "implicit asterisk" expansion, as below */
1440 if (sub.l == 0) {
1441 sub.s = UNCONST("*");
1442 sub.l = 1;
1443 } else {
1444 exp.s = salloc(sub.l + 1 +1);
1445 memcpy(exp.s, sub.s, sub.l);
1446 exp.s[sub.l] = '\0';
1447 sub.s = exp.s;
1450 /* TODO there is a TODO note upon fexpand() with multi-return;
1451 * TODO if that will change, the if() below can be simplified */
1452 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1453 jredo:
1454 hold_all_sigs();
1455 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1456 rele_all_sigs();
1458 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1459 goto jnope;
1460 /* If the expansion equals the original string, assume the user wants what
1461 * is usually known as tab completion, append `*' and restart */
1462 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1463 if (sub.s[sub.l - 1] == '*')
1464 goto jnope;
1465 sub.s[sub.l++] = '*';
1466 sub.s[sub.l] = '\0';
1467 goto jredo;
1470 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1471 * Take care to take *prompt* into account, since we don't know
1472 * anything about it's visual length (fputs(3) is used), simply
1473 * assume each character requires two columns */
1474 /* TODO the problem is that we loose control otherwise; in the best
1475 * TODO case the user can control via ^A and ^K etc., but be safe;
1476 * TODO we cannot simply adjust fexpand() because we don't know how
1477 * TODO that is implemented... The real solution would be to check
1478 * TODO wether we fit on a line, and start a pager if not.
1479 * TODO However, that should be part of a real tab-COMPLETION, then,
1480 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1481 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1482 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1483 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1484 char const e1[] = "[ERR_TOO_LONG]";
1485 exp.s = UNCONST(e1);
1486 exp.l = sizeof(e1) - 1;
1487 topp.l = 0;
1488 if (rv + bot.l + exp.l >= MAX_INPUT)
1489 bot.l = 0;
1490 if (rv + exp.l >= MAX_INPUT) {
1491 char const e2[] = "[ERR]";
1492 exp.s = UNCONST(e2);
1493 exp.l = sizeof(e2) - 1;
1497 orig.l = bot.l + exp.l + topp.l;
1498 orig.s = salloc(orig.l + 5 +1);
1499 if ((rv = bot.l) > 0)
1500 memcpy(orig.s, bot.s, rv);
1501 memcpy(orig.s + rv, exp.s, exp.l);
1502 rv += exp.l;
1503 if (topp.l > 0) {
1504 memcpy(orig.s + rv, topp.s, topp.l);
1505 rv += topp.l;
1507 orig.s[rv] = '\0';
1509 l->defc = orig;
1510 _ncl_khome(l, FAL0);
1511 _ncl_kkill(l, FAL0);
1512 jleave:
1513 NYD_LEAVE;
1514 return rv;
1515 jnope:
1516 /* If we've provided a default content, but failed to expand, there is
1517 * nothing we can "revert to": drop that default again */
1518 if (set_savec)
1519 l->savec.s = NULL, l->savec.l = 0;
1520 rv = 0;
1521 goto jleave;
1523 # endif /* HAVE_TABEXPAND */
1525 static ssize_t
1526 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1527 SMALLOC_DEBUG_ARGS)
1529 /* We want to save code, yet we may have to incorporate a lines'
1530 * default content and / or default input to switch back to after some
1531 * history movement; let "len > 0" mean "have to display some data
1532 * buffer", and only otherwise read(2) it */
1533 mbstate_t ps[2];
1534 struct line l;
1535 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
1536 wchar_t wc;
1537 ssize_t rv;
1538 ui32_t maybe_cursor;
1539 NYD_ENTER;
1541 memset(&l, 0, sizeof l);
1542 l.line.cbuf = *buf;
1543 if (len != 0) {
1544 l.defc.s = savestrbuf(*buf, len);
1545 l.defc.l = len;
1547 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1548 l.prompt = prompt = "?ERR?";
1549 /* TODO *l.nd=='\0' : instead adjust acmava.c to disallow empty vals */
1550 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1551 l.nd = "\033[C"; /* XXX no "magic" constant */
1552 l.x_buf = buf;
1553 l.x_bufsize = bufsize;
1555 if (prompt != NULL && *prompt != '\0') {
1556 fputs(prompt, stdout);
1557 fflush(stdout);
1559 jrestart:
1560 memset(ps, 0, sizeof ps);
1561 maybe_cursor = 0;
1562 /* TODO: NCL: we should output the reset sequence when we jrestart:
1563 * TODO: NCL: if we are using a stateful encoding? !
1564 * TODO: NCL: in short: this is not yet well understood */
1565 for (;;) {
1566 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1568 /* Normal read(2)? Else buffer-takeover: speed this one up */
1569 if (len == 0)
1570 cbufp =
1571 cbuf = cbuf_base;
1572 else {
1573 assert(l.defc.l > 0 && l.defc.s != NULL);
1574 cbufp =
1575 cbuf = l.defc.s + (l.defc.l - len);
1576 cbufp += len;
1579 /* Read in the next complete multibyte character */
1580 for (;;) {
1581 if (len == 0) {
1582 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1583 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1584 continue;
1585 goto jleave;
1587 ++cbufp;
1590 /* Ach! the ISO C multibyte handling!
1591 * Encodings with locking shift states cannot really be helped, since
1592 * it is impossible to only query the shift state, as opposed to the
1593 * entire shift state + character pair (via ISO C functions) */
1594 rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), ps + 0);
1595 if (rv <= 0) {
1596 /* Any error during take-over can only result in a hard reset;
1597 * Otherwise, if it's a hard error, or if too many redundant shift
1598 * sequences overflow our buffer, also perform a hard reset */
1599 if (len != 0 || rv == -1 ||
1600 sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)) {
1601 l.savec.s = l.defc.s = NULL,
1602 l.savec.l = l.defc.l = len = 0;
1603 putchar('\a');
1604 wc = 'G';
1605 goto jreset;
1607 /* Otherwise, due to the way we deal with the buffer, we need to
1608 * restore the mbstate_t from before this conversion */
1609 ps[0] = ps[1];
1610 continue;
1613 if (len != 0 && (len -= (size_t)rv) == 0)
1614 l.defc.s = NULL, l.defc.l = 0;
1615 ps[1] = ps[0];
1616 break;
1619 /* Don't interpret control bytes during buffer take-over */
1620 if (cbuf != cbuf_base)
1621 goto jprint;
1622 switch (wc) {
1623 case 'A' ^ 0x40: /* cursor home */
1624 _ncl_khome(&l, TRU1);
1625 break;
1626 j_b:
1627 case 'B' ^ 0x40: /* backward character */
1628 _ncl_kleft(&l);
1629 break;
1630 /* 'C': interrupt (CTRL-C) */
1631 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1632 if ((rv = _ncl_keof(&l)) < 0)
1633 goto jleave;
1634 break;
1635 case 'E' ^ 0x40: /* end of line */
1636 _ncl_kend(&l);
1637 break;
1638 j_f:
1639 case 'F' ^ 0x40: /* forward character */
1640 _ncl_kright(&l);
1641 break;
1642 /* 'G' below */
1643 case 'H' ^ 0x40: /* backspace */
1644 case '\177':
1645 _ncl_kbs(&l);
1646 break;
1647 case 'I' ^ 0x40: /* horizontal tab */
1648 # ifdef HAVE_TABEXPAND
1649 if ((len = _ncl_kht(&l)) > 0)
1650 goto jrestart;
1651 # endif
1652 goto jbell;
1653 case 'J' ^ 0x40: /* NL (\n) */
1654 goto jdone;
1655 case 'G' ^ 0x40: /* full reset */
1656 jreset:
1657 /* FALLTHRU */
1658 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1659 _ncl_khome(&l, FAL0);
1660 /* FALLTHRU */
1661 case 'K' ^ 0x40: /* kill from cursor to end of line */
1662 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1663 /* (Handle full reset?) */
1664 if (wc == ('G' ^ 0x40)) {
1665 # ifdef HAVE_HISTORY
1666 l.hist = NULL;
1667 # endif
1668 if ((len = l.savec.l) != 0) {
1669 l.defc = l.savec;
1670 l.savec.s = NULL, l.savec.l = 0;
1671 } else
1672 len = l.defc.l;
1674 fflush(stdout);
1675 goto jrestart;
1676 case 'L' ^ 0x40: /* repaint line */
1677 _ncl_krefresh(&l);
1678 break;
1679 /* 'M': CR (\r) */
1680 j_n:
1681 case 'N' ^ 0x40: /* history next */
1682 # ifdef HAVE_HISTORY
1683 if (l.hist == NULL)
1684 goto jbell;
1685 if ((len = _ncl_khist(&l, FAL0)) > 0)
1686 goto jrestart;
1687 wc = 'G' ^ 0x40;
1688 goto jreset;
1689 # else
1690 goto jbell;
1691 # endif
1692 /* 'O' */
1693 j_p:
1694 case 'P' ^ 0x40: /* history previous */
1695 # ifdef HAVE_HISTORY
1696 if ((len = _ncl_khist(&l, TRU1)) > 0)
1697 goto jrestart;
1698 wc = 'G' ^ 0x40;
1699 goto jreset;
1700 # else
1701 goto jbell;
1702 # endif
1703 /* 'Q': no code */
1704 case 'R' ^ 0x40: /* reverse history search */
1705 # ifdef HAVE_HISTORY
1706 if ((len = _ncl_krhist(&l)) > 0)
1707 goto jrestart;
1708 wc = 'G' ^ 0x40;
1709 goto jreset;
1710 # else
1711 goto jbell;
1712 # endif
1713 /* 'S': no code */
1714 /* 'U' above */
1715 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1716 case 'W' ^ 0x40: /* backward delete "word" */
1717 _ncl_kbwddelw(&l);
1718 break;
1719 case 'X' ^ 0x40: /* move cursor forward "word" */
1720 _ncl_kgow(&l, +1);
1721 break;
1722 case 'Y' ^ 0x40: /* move cursor backward "word" */
1723 _ncl_kgow(&l, -1);
1724 break;
1725 /* 'Z': suspend (CTRL-Z) */
1726 case 0x1B:
1727 if (maybe_cursor++ != 0)
1728 goto jreset;
1729 continue;
1730 default:
1731 /* XXX Handle usual ^[[[ABCD] cursor keys -- UGLY, "MAGIC", INFLEX */
1732 if (maybe_cursor > 0) {
1733 if (++maybe_cursor == 2) {
1734 if (wc == L'[')
1735 continue;
1736 maybe_cursor = 0;
1737 } else {
1738 maybe_cursor = 0;
1739 switch (wc) {
1740 case L'A': goto j_p;
1741 case L'B': goto j_n;
1742 case L'C': goto j_f;
1743 case L'D': goto j_b;
1745 _ncl_kother(&l, L'[');
1748 jprint:
1749 if (iswprint(wc)) {
1750 _ncl_kother(&l, wc);
1751 /* Don't clear the history during takeover..
1752 * ..and also avoid fflush()ing unless we've worked entire buffer */
1753 if (len > 0)
1754 continue;
1755 # ifdef HAVE_HISTORY
1756 if (cbuf == cbuf_base)
1757 l.hist = NULL;
1758 # endif
1759 } else {
1760 jbell:
1761 putchar('\a');
1763 break;
1765 fflush(stdout);
1768 /* We have a completed input line, convert the struct cell data to its
1769 * plain character equivalent */
1770 jdone:
1771 putchar('\n');
1772 fflush(stdout);
1773 len = _ncl_cell2dat(&l);
1774 rv = (ssize_t)len;
1775 jleave:
1776 NYD_LEAVE;
1777 return rv;
1780 FL void
1781 tty_init(void)
1783 # ifdef HAVE_HISTORY
1784 long hs;
1785 char *v, *lbuf;
1786 FILE *f;
1787 size_t lsize, cnt, llen;
1788 # endif
1789 NYD_ENTER;
1791 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1792 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1793 _ncl_ottou.sint = -1;
1795 # ifdef HAVE_HISTORY
1796 _CL_HISTSIZE(hs);
1797 _ncl_hist_size = 0;
1798 _ncl_hist_size_max = hs;
1799 if (hs == 0)
1800 goto jleave;
1802 _CL_HISTFILE(v);
1803 if (v == NULL)
1804 goto jleave;
1806 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1807 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1808 if (f == NULL)
1809 goto jdone;
1810 fcntl_lock(fileno(f), FLOCK_READ); /* TODO ouch, retval check, etc. */
1812 lbuf = NULL;
1813 lsize = 0;
1814 cnt = fsize(f);
1815 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1816 if (llen > 0 && lbuf[llen - 1] == '\n')
1817 lbuf[--llen] = '\0';
1818 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1819 continue;
1820 _ncl_hist_load = TRU1;
1821 tty_addhist(lbuf);
1822 _ncl_hist_load = FAL0;
1824 if (lbuf != NULL)
1825 free(lbuf);
1827 fclose(f);
1828 jdone:
1829 rele_all_sigs(); /* XXX remove jumps */
1830 jleave:
1831 # endif /* HAVE_HISTORY */
1832 NYD_LEAVE;
1835 FL void
1836 tty_destroy(void)
1838 # ifdef HAVE_HISTORY
1839 long hs;
1840 char *v;
1841 struct hist *hp;
1842 FILE *f;
1843 # endif
1844 NYD_ENTER;
1846 # ifdef HAVE_HISTORY
1847 _CL_HISTSIZE(hs);
1848 if (hs == 0)
1849 goto jleave;
1851 _CL_HISTFILE(v);
1852 if (v == NULL)
1853 goto jleave;
1855 if ((hp = _ncl_hist) != NULL)
1856 while (hp->older != NULL && hs-- != 0)
1857 hp = hp->older;
1859 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1860 f = fopen(v, "w"); /* TODO temporary + rename?! */
1861 if (f == NULL)
1862 goto jdone;
1863 fcntl_lock(fileno(f), FLOCK_WRITE); /* TODO ouch, retval check, etc. */
1864 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1865 goto jclose;
1867 for (; hp != NULL; hp = hp->younger) {
1868 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1869 putc('\n', f);
1871 jclose:
1872 fclose(f);
1873 jdone:
1874 rele_all_sigs(); /* XXX remove jumps */
1875 jleave:
1876 # endif /* HAVE_HISTORY */
1877 NYD_LEAVE;
1880 FL void
1881 tty_signal(int sig)
1883 sigset_t nset, oset;
1884 NYD_X; /* Signal handler */
1886 switch (sig) {
1887 case SIGWINCH:
1888 /* We don't deal with SIGWINCH, yet get called from main.c */
1889 break;
1890 default:
1891 _ncl_term_mode(FAL0);
1892 _ncl_sigs_down();
1893 sigemptyset(&nset);
1894 sigaddset(&nset, sig);
1895 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1896 kill(0, sig);
1897 /* When we come here we'll continue editing, so reestablish */
1898 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1899 _ncl_sigs_up();
1900 _ncl_term_mode(TRU1);
1901 break;
1905 FL int
1906 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1907 SMALLOC_DEBUG_ARGS)
1909 ssize_t nn;
1910 NYD_ENTER;
1912 /* Of course we have races here, but they cannot be avoided on POSIX
1913 * (except by even *more* actions) */
1914 _ncl_sigs_up();
1915 _ncl_term_mode(TRU1);
1916 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1917 _ncl_term_mode(FAL0);
1918 _ncl_sigs_down();
1919 NYD_LEAVE;
1920 return (int)nn;
1923 FL void
1924 tty_addhist(char const *s)
1926 # ifdef HAVE_HISTORY
1927 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1928 size_t l;
1929 struct hist *h, *o, *y;
1930 # endif
1931 NYD_ENTER;
1932 UNUSED(s);
1934 # ifdef HAVE_HISTORY
1935 l = strlen(s);
1937 if (_ncl_hist_size_max == 0)
1938 goto j_leave;
1939 _CL_CHECK_ADDHIST(s, goto j_leave);
1941 /* Eliminating duplicates is expensive, but simply inacceptable so
1942 * during the load of a potentially large history file! */
1943 if (!_ncl_hist_load)
1944 for (h = _ncl_hist; h != NULL; h = h->older)
1945 if (h->len == l && !strcmp(h->dat, s)) {
1946 hold_all_sigs(); /* TODO */
1947 o = h->older;
1948 y = h->younger;
1949 if (o != NULL)
1950 o->younger = y;
1951 else
1952 _ncl_hist_tail = y;
1953 if (y != NULL)
1954 y->older = o;
1955 else
1956 _ncl_hist = o;
1957 goto jleave;
1959 hold_all_sigs();
1961 ++_ncl_hist_size;
1962 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1963 --_ncl_hist_size;
1964 if ((h = _ncl_hist_tail) != NULL) {
1965 if ((_ncl_hist_tail = h->younger) == NULL)
1966 _ncl_hist = NULL;
1967 else
1968 _ncl_hist_tail->older = NULL;
1969 free(h);
1973 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1974 h->len = l;
1975 memcpy(h->dat, s, l +1);
1976 jleave:
1977 if ((h->older = _ncl_hist) != NULL)
1978 _ncl_hist->younger = h;
1979 else
1980 _ncl_hist_tail = h;
1981 h->younger = NULL;
1982 _ncl_hist = h;
1984 rele_all_sigs();
1985 j_leave:
1986 # endif
1987 NYD_LEAVE;
1990 # ifdef HAVE_HISTORY
1991 FL int
1992 c_history(void *v)
1994 C_HISTORY_SHARED;
1996 jlist: {
1997 FILE *fp;
1998 size_t i, b;
1999 struct hist *h;
2001 if (_ncl_hist == NULL)
2002 goto jleave;
2004 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
2005 NULL) {
2006 perror("tmpfile");
2007 v = NULL;
2008 goto jleave;
2011 i = _ncl_hist_size;
2012 b = 0;
2013 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
2014 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
2015 (ul_it)i, h->dat, (ul_it)b, (ul_it)h->len);
2017 page_or_print(fp, i);
2018 Fclose(fp);
2020 goto jleave;
2022 jclear: {
2023 struct hist *h;
2024 while ((h = _ncl_hist) != NULL) {
2025 _ncl_hist = h->older;
2026 free(h);
2028 _ncl_hist_tail = NULL;
2029 _ncl_hist_size = 0;
2031 goto jleave;
2033 jentry: {
2034 struct hist *h = _ncl_hist;
2035 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2036 entry = (long)_ncl_hist_size - entry;
2037 for (h = _ncl_hist;; h = h->older)
2038 if (h == NULL)
2039 break;
2040 else if (entry-- != 0)
2041 continue;
2042 else {
2043 v = temporary_arg_v_store = h->dat;
2044 goto jleave;
2047 v = NULL;
2049 goto jleave;
2051 # endif /* HAVE_HISTORY */
2052 #endif /* HAVE_NCL */
2055 * The really-nothing-at-all implementation
2058 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2059 FL void
2060 tty_init(void)
2062 NYD_ENTER;
2063 NYD_LEAVE;
2066 FL void
2067 tty_destroy(void)
2069 NYD_ENTER;
2070 NYD_LEAVE;
2073 FL void
2074 tty_signal(int sig)
2076 NYD_X; /* Signal handler */
2077 UNUSED(sig);
2080 FL int
2081 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2082 SMALLOC_DEBUG_ARGS)
2084 /* TODO The nothing-at-all tty layer even forces re-entering all the
2085 * TODO original data when re-editing a field */
2086 bool_t doffl = FAL0;
2087 int rv;
2088 NYD_ENTER;
2090 if (prompt != NULL && *prompt != '\0') {
2091 fputs(prompt, stdout);
2092 doffl = TRU1;
2094 if (n > 0) {
2095 fprintf(stdout, tr(511, "{former content: %.*s} "), (int)n, *linebuf);
2096 n = 0;
2097 doffl = TRU1;
2099 if (doffl)
2100 fflush(stdout);
2101 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2102 NYD_LEAVE;
2103 return rv;
2106 FL void
2107 tty_addhist(char const *s)
2109 NYD_ENTER;
2110 UNUSED(s);
2111 NYD_LEAVE;
2113 #endif /* nothing at all */
2115 /* vim:set fenc=utf-8:s-it-mode */