Drop SEND_TOFLTR, S-nail passes raw data through to spam filter
[s-mailx.git] / tty.c
blob41fec4487507ceb3447cda7600add28b66baab4f
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 FL char *
182 getuser(char const * volatile query) /* TODO v15-compat obsolete */
184 sighandler_type volatile ohdl;
185 char * volatile user = NULL;
186 bool_t volatile hadsig = FAL0;
187 NYD_ENTER;
189 if (query == NULL)
190 query = _("User: ");
192 ohdl = safe_signal(SIGINT, SIG_IGN);
193 if (sigsetjmp(__tty_actjmp, 1) != 0) {
194 hadsig = TRU1;
195 goto jrestore;
197 safe_signal(SIGINT, &__tty_acthdl);
199 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
200 &termios_state.ts_linesize, NULL) >= 0)
201 user = termios_state.ts_linebuf;
202 jrestore:
203 termios_state_reset();
204 safe_signal(SIGINT, ohdl);
205 NYD_LEAVE;
206 if (hadsig && ohdl != SIG_IGN)
207 kill(0, SIGINT);
208 return user;
211 FL char *
212 getpassword(char const *query)
214 sighandler_type volatile ohdl;
215 struct termios tios;
216 char * volatile pass = NULL;
217 #if 0
218 bool_t hadsig = FAL0; /* TODO getpassword() no longer reraises SIGINT */
219 #endif
220 NYD_ENTER;
222 if (query == NULL)
223 query = _("Password: ");
224 fputs(query, stdout);
225 fflush(stdout);
227 /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
228 * FIXME foreground pgrp, and can fail with EINTR!! also affects
229 * FIXME termios_state_reset() */
230 if (options & OPT_TTYIN) {
231 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
232 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
233 termios_state.ts_needs_reset = TRU1;
234 tios.c_iflag &= ~(ISTRIP);
235 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
236 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
239 ohdl = safe_signal(SIGINT, SIG_IGN);
240 if (sigsetjmp(__tty_actjmp, 1) != 0) {
241 #if 0
242 hadsig = TRU1;
243 #endif
244 goto jrestore;
246 safe_signal(SIGINT, &__tty_acthdl);
248 if (readline_restart(stdin, &termios_state.ts_linebuf,
249 &termios_state.ts_linesize, 0) >= 0)
250 pass = termios_state.ts_linebuf;
251 jrestore:
252 termios_state_reset();
253 safe_signal(SIGINT, ohdl);
254 if (options & OPT_TTYIN)
255 fputc('\n', stdout);
256 NYD_LEAVE;
257 #if 0
258 if (hadsig && ohdl != SIG_IGN)
259 kill(0, SIGINT);
260 #endif
261 return pass;
265 * readline(3)
268 #ifdef HAVE_READLINE
269 static sighandler_type _rl_shup;
270 static char * _rl_buf; /* pre_input() hook: initial line */
271 static int _rl_buflen; /* content, and its length */
273 static int _rl_pre_input(void);
275 static int
276 _rl_pre_input(void)
278 NYD_ENTER;
279 /* Handle leftover data from \ escaped former line */
280 rl_extend_line_buffer(_rl_buflen + 10);
281 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
282 rl_point = rl_end = _rl_buflen;
283 rl_pre_input_hook = (rl_hook_func_t*)NULL;
284 rl_redisplay();
285 NYD_LEAVE;
286 return 0;
289 FL void
290 tty_init(void)
292 # ifdef HAVE_HISTORY
293 long hs;
294 char *v;
295 # endif
296 NYD_ENTER;
298 rl_readline_name = UNCONST(uagent);
299 # ifdef HAVE_HISTORY
300 _CL_HISTSIZE(hs);
301 using_history();
302 stifle_history((int)hs);
303 # endif
304 rl_read_init_file(NULL);
306 /* Because rl_read_init_file() may have introduced yet a different
307 * history size limit, simply load and incorporate the history, leave
308 * it up to readline(3) to do the rest */
309 # ifdef HAVE_HISTORY
310 _CL_HISTFILE(v);
311 if (v != NULL)
312 read_history(v);
313 # endif
314 NYD_LEAVE;
317 FL void
318 tty_destroy(void)
320 # ifdef HAVE_HISTORY
321 char *v;
322 # endif
323 NYD_ENTER;
325 # ifdef HAVE_HISTORY
326 _CL_HISTFILE(v);
327 if (v != NULL)
328 write_history(v);
329 # endif
330 NYD_LEAVE;
333 FL void
334 tty_signal(int sig)
336 sigset_t nset, oset;
337 NYD_X; /* Signal handler */
339 switch (sig) {
340 # ifdef SIGWINCH
341 case SIGWINCH:
342 break;
343 # endif
344 case SIGHUP:
345 /* readline(3) doesn't catch it :( */
346 rl_free_line_state();
347 rl_cleanup_after_signal();
348 safe_signal(SIGHUP, _rl_shup);
349 sigemptyset(&nset);
350 sigaddset(&nset, sig);
351 sigprocmask(SIG_UNBLOCK, &nset, &oset);
352 kill(0, sig);
353 /* XXX When we come here we'll continue editing, so reestablish
354 * XXX cannot happen */
355 sigprocmask(SIG_BLOCK, &oset, NULL);
356 _rl_shup = safe_signal(SIGHUP, &tty_signal);
357 rl_reset_after_signal();
358 break;
359 default:
360 break;
364 FL int
365 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
366 SMALLOC_DEBUG_ARGS)
368 int nn;
369 char *line;
370 NYD_ENTER;
372 if (n > 0) {
373 _rl_buf = *linebuf;
374 _rl_buflen = (int)n;
375 rl_pre_input_hook = &_rl_pre_input;
378 _rl_shup = safe_signal(SIGHUP, &tty_signal);
379 line = readline(prompt != NULL ? prompt : "");
380 safe_signal(SIGHUP, _rl_shup);
382 if (line == NULL) {
383 nn = -1;
384 goto jleave;
386 n = strlen(line);
388 if (n >= *linesize) {
389 *linesize = LINESIZE + n +1;
390 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
392 memcpy(*linebuf, line, n);
393 (free)(line);
394 (*linebuf)[n] = '\0';
395 nn = (int)n;
396 jleave:
397 NYD_LEAVE;
398 return nn;
401 FL void
402 tty_addhist(char const *s, bool_t isgabby)
404 NYD_ENTER;
405 UNUSED(s);
406 UNUSED(isgabby);
407 # ifdef HAVE_HISTORY
408 if (isgabby && !ok_blook(history_gabby))
409 goto jleave;
410 _CL_CHECK_ADDHIST(s, goto jleave);
411 hold_all_sigs(); /* XXX too heavy */
412 add_history(s); /* XXX yet we jump away! */
413 rele_all_sigs(); /* XXX remove jumps */
414 jleave:
415 # endif
416 NYD_LEAVE;
419 # ifdef HAVE_HISTORY
420 FL int
421 c_history(void *v)
423 C_HISTORY_SHARED;
425 jlist: {
426 FILE *fp;
427 HISTORY_STATE *hs;
428 HIST_ENTRY **hl;
429 ul_it i, b;
431 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
432 NULL) {
433 perror("tmpfile");
434 v = NULL;
435 goto jleave;
438 hs = history_get_history_state();
440 for (i = (ul_it)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
441 char *cp = (*--hl)->line;
442 size_t sl = strlen(cp);
443 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n", i, cp, b, sl);
444 b += sl;
447 page_or_print(fp, (size_t)hs->length);
448 Fclose(fp);
450 goto jleave;
452 jclear:
453 clear_history();
454 goto jleave;
456 jentry: {
457 HISTORY_STATE *hs = history_get_history_state();
459 if (UICMP(z, entry, <=, hs->length))
460 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
461 else
462 v = NULL;
464 goto jleave;
466 # endif /* HAVE_HISTORY */
467 #endif /* HAVE_READLINE */
470 * BSD editline(3)
473 #ifdef HAVE_EDITLINE
474 static EditLine * _el_el; /* editline(3) handle */
475 static char const * _el_prompt; /* Current prompt */
476 # ifdef HAVE_HISTORY
477 static History * _el_hcom; /* History handle for commline */
478 # endif
480 static char const * _el_getprompt(void);
482 static char const *
483 _el_getprompt(void)
485 return _el_prompt;
488 FL void
489 tty_init(void)
491 # ifdef HAVE_HISTORY
492 HistEvent he;
493 long hs;
494 char *v;
495 # endif
496 NYD_ENTER;
498 # ifdef HAVE_HISTORY
499 _CL_HISTSIZE(hs);
500 _el_hcom = history_init();
501 history(_el_hcom, &he, H_SETSIZE, (int)hs);
502 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
503 # endif
505 _el_el = el_init(uagent, stdin, stdout, stderr);
506 el_set(_el_el, EL_SIGNAL, 1);
507 el_set(_el_el, EL_TERMINAL, NULL);
508 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
509 # ifdef HAVE_HISTORY
510 el_set(_el_el, EL_HIST, &history, _el_hcom);
511 # endif
512 el_set(_el_el, EL_EDITOR, "emacs");
513 # ifdef EL_PROMPT_ESC
514 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
515 # else
516 el_set(_el_el, EL_PROMPT, &_el_getprompt);
517 # endif
518 # if 0
519 el_set(_el_el, EL_ADDFN, "tab_complete",
520 "editline(3) internal completion function", &_el_file_cpl);
521 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
522 # endif
523 # ifdef HAVE_HISTORY
524 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
525 # endif
526 el_source(_el_el, NULL); /* Source ~/.editrc */
528 /* Because el_source() may have introduced yet a different history size
529 * limit, simply load and incorporate the history, leave it up to
530 * editline(3) to do the rest */
531 # ifdef HAVE_HISTORY
532 _CL_HISTFILE(v);
533 if (v != NULL)
534 history(_el_hcom, &he, H_LOAD, v);
535 # endif
536 NYD_LEAVE;
539 FL void
540 tty_destroy(void)
542 # ifdef HAVE_HISTORY
543 HistEvent he;
544 char *v;
545 # endif
546 NYD_ENTER;
548 el_end(_el_el);
550 # ifdef HAVE_HISTORY
551 _CL_HISTFILE(v);
552 if (v != NULL)
553 history(_el_hcom, &he, H_SAVE, v);
554 history_end(_el_hcom);
555 # endif
556 NYD_LEAVE;
559 FL void
560 tty_signal(int sig)
562 NYD_X; /* Signal handler */
563 switch (sig) {
564 # ifdef SIGWINCH
565 case SIGWINCH:
566 el_resize(_el_el);
567 break;
568 # endif
569 default:
570 break;
574 FL int
575 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
576 SMALLOC_DEBUG_ARGS)
578 int nn;
579 char const *line;
580 NYD_ENTER;
582 _el_prompt = (prompt != NULL) ? prompt : "";
583 if (n > 0)
584 el_push(_el_el, *linebuf);
585 line = el_gets(_el_el, &nn);
587 if (line == NULL) {
588 nn = -1;
589 goto jleave;
591 assert(nn >= 0);
592 n = (size_t)nn;
593 if (n > 0 && line[n - 1] == '\n')
594 nn = (int)--n;
596 if (n >= *linesize) {
597 *linesize = LINESIZE + n + 1;
598 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
600 memcpy(*linebuf, line, n);
601 (*linebuf)[n] = '\0';
602 jleave:
603 NYD_LEAVE;
604 return nn;
607 FL void
608 tty_addhist(char const *s, bool_t isgabby)
610 # ifdef HAVE_HISTORY
611 /* Enlarge meaning of unique .. to something that rocks;
612 * xxx unfortunately this is expensive to do with editline(3)
613 * xxx maybe it would be better to hook the ptfs instead? */
614 HistEvent he;
615 int i;
616 # endif
617 NYD_ENTER;
618 UNUSED(s);
619 UNUSED(isgabby);
621 # ifdef HAVE_HISTORY
622 if (isgabby && !ok_blook(history_gabby))
623 goto jleave;
624 _CL_CHECK_ADDHIST(s, goto jleave);
626 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
627 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
628 i = history(_el_hcom, &he, H_NEXT))
629 if (!strcmp(he.str, s)) {
630 history(_el_hcom, &he, H_DEL, he.num);
631 break;
633 history(_el_hcom, &he, H_ENTER, s);
634 rele_all_sigs(); /* XXX remove jumps */
635 jleave:
636 # endif
637 NYD_LEAVE;
640 # ifdef HAVE_HISTORY
641 FL int
642 c_history(void *v)
644 C_HISTORY_SHARED;
646 jlist: {
647 HistEvent he;
648 FILE *fp;
649 size_t i, b;
650 int x;
652 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
653 NULL) {
654 perror("tmpfile");
655 v = NULL;
656 goto jleave;
659 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
660 b = 0;
661 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
662 x = history(_el_hcom, &he, H_NEXT)) {
663 size_t sl = strlen(he.str);
664 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n",
665 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
666 --i;
667 b += sl;
670 page_or_print(fp, i);
671 Fclose(fp);
673 goto jleave;
675 jclear: {
676 HistEvent he;
677 history(_el_hcom, &he, H_CLEAR);
679 goto jleave;
681 jentry: {
682 HistEvent he;
683 size_t i;
684 int x;
686 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
687 if (UICMP(z, entry, <=, i)) {
688 entry = (long)i - entry;
689 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
690 x = history(_el_hcom, &he, H_NEXT))
691 if (entry-- == 0) {
692 v = temporary_arg_v_store = UNCONST(he.str);
693 goto jleave;
696 v = NULL;
698 goto jleave;
700 # endif /* HAVE_HISTORY */
701 #endif /* HAVE_EDITLINE */
704 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
706 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
707 * We do not handle character widths because the terminal must deal with that
708 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
709 * characters by definition on the other. We're addicted.
711 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
712 * we're forced to use the very same buffer--the one that is passed through to
713 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
714 * convert that on-the-fly back to the plain char* result once we're done.
715 * To simplify our live, use savestr() buffers for all other needed memory
719 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
720 * TODO NCL: during handler de-/installation handling.
723 #ifdef HAVE_NCL
724 # ifndef MAX_INPUT
725 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
726 # endif
728 /* Since we simply fputs(3) the prompt, assume each character requires two
729 * visual cells -- and we need to restrict the maximum prompt size because
730 * of MAX_INPUT and our desire to have room for some error message left */
731 # define _PROMPT_VLEN(P) (strlen(P) * 2)
732 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
734 union xsighdl {
735 sighandler_type shdl; /* Try avoid races by setting */
736 sl_it sint; /* .sint=-1 when inactive */
738 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
740 struct xtios {
741 struct termios told;
742 struct termios tnew;
745 struct cell {
746 wchar_t wc;
747 ui32_t count;
748 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
751 struct line {
752 size_t cursor; /* Current cursor position */
753 size_t topins; /* Outermost cursor col set */
754 union {
755 char *cbuf; /* *x_buf */
756 struct cell *cells;
757 } line;
758 struct str defc; /* Current default content */
759 struct str savec; /* Saved default content */
760 # ifdef HAVE_HISTORY
761 struct hist *hist; /* History cursor */
762 # endif
763 char const *prompt;
764 char const *nd; /* Cursor right */
765 char **x_buf; /* Caller pointers */
766 size_t *x_bufsize;
769 # ifdef HAVE_HISTORY
770 struct hist {
771 struct hist *older;
772 struct hist *younger;
773 ui32_t isgabby : 1;
774 ui32_t len : 31;
775 char dat[VFIELD_SIZE(sizeof(ui32_t))];
777 # endif
779 static union xsighdl _ncl_oint;
780 static union xsighdl _ncl_oquit;
781 static union xsighdl _ncl_oterm;
782 static union xsighdl _ncl_ohup;
783 static union xsighdl _ncl_otstp;
784 static union xsighdl _ncl_ottin;
785 static union xsighdl _ncl_ottou;
786 static struct xtios _ncl_tios;
787 # ifdef HAVE_HISTORY
788 static struct hist *_ncl_hist;
789 static struct hist *_ncl_hist_tail;
790 static size_t _ncl_hist_size;
791 static size_t _ncl_hist_size_max;
792 static bool_t _ncl_hist_load;
793 # endif
795 static void _ncl_sigs_up(void);
796 static void _ncl_sigs_down(void);
798 static void _ncl_term_mode(bool_t raw);
800 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
801 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
802 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
803 static ssize_t _ncl_cell2dat(struct line *l);
804 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
805 static void _ncl_cell2save(struct line *l);
806 # endif
808 static void _ncl_khome(struct line *l, bool_t dobell);
809 static void _ncl_kend(struct line *l);
810 static void _ncl_kbs(struct line *l);
811 static void _ncl_kkill(struct line *l, bool_t dobell);
812 static ssize_t _ncl_keof(struct line *l);
813 static void _ncl_kleft(struct line *l);
814 static void _ncl_kright(struct line *l);
815 static void _ncl_krefresh(struct line *l);
816 static void _ncl_kbwddelw(struct line *l);
817 static void _ncl_kgow(struct line *l, ssize_t dir);
818 static void _ncl_kother(struct line *l, wchar_t wc);
819 # ifdef HAVE_HISTORY
820 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
821 static size_t _ncl_khist(struct line *l, bool_t backwd);
822 static size_t _ncl_krhist(struct line *l);
823 # endif
824 # ifdef HAVE_TABEXPAND
825 static size_t _ncl_kht(struct line *l);
826 # endif
827 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
828 size_t len SMALLOC_DEBUG_ARGS);
830 static void
831 _ncl_sigs_up(void)
833 NYD_ENTER;
834 if (_ncl_oint.sint == -1)
835 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
836 if (_ncl_oquit.sint == -1)
837 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
838 if (_ncl_oterm.sint == -1)
839 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
840 if (_ncl_ohup.sint == -1)
841 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
842 if (_ncl_otstp.sint == -1)
843 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
844 if (_ncl_ottin.sint == -1)
845 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
846 if (_ncl_ottou.sint == -1)
847 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
848 NYD_LEAVE;
851 static void
852 _ncl_sigs_down(void)
854 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
855 sighandler_type st;
856 NYD_ENTER;
858 if (_ncl_ottou.sint != -1) {
859 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
860 safe_signal(SIGTTOU, st);
862 if (_ncl_ottin.sint != -1) {
863 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
864 safe_signal(SIGTTIN, st);
866 if (_ncl_otstp.sint != -1) {
867 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
868 safe_signal(SIGTSTP, st);
870 if (_ncl_ohup.sint != -1) {
871 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
872 safe_signal(SIGHUP, st);
874 if (_ncl_oterm.sint != -1) {
875 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
876 safe_signal(SIGTERM, st);
878 if (_ncl_oquit.sint != -1) {
879 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
880 safe_signal(SIGQUIT, st);
882 if (_ncl_oint.sint != -1) {
883 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
884 safe_signal(SIGINT, st);
886 NYD_LEAVE;
889 static void
890 _ncl_term_mode(bool_t raw)
892 struct termios *tiosp;
893 NYD_ENTER;
895 tiosp = &_ncl_tios.told;
896 if (!raw)
897 goto jleave;
899 /* Always requery the attributes, in case we've been moved from background
900 * to foreground or however else in between sessions */
901 tcgetattr(STDIN_FILENO, tiosp);
902 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
903 tiosp = &_ncl_tios.tnew;
904 tiosp->c_cc[VMIN] = 1;
905 tiosp->c_cc[VTIME] = 0;
906 tiosp->c_iflag &= ~(ISTRIP);
907 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
908 jleave:
909 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
910 NYD_LEAVE;
913 static void
914 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
916 size_t i;
917 NYD_ENTER;
919 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
920 if (i > *l->x_bufsize) {
921 i <<= 1;
922 *l->x_bufsize = i;
923 l->line.cbuf =
924 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
926 NYD_LEAVE;
929 static void
930 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
932 size_t j;
933 NYD_ENTER;
935 if (i > 0)
936 memmove(cap, cap + 1, i * sizeof(*cap));
938 /* And.. the (rest of the) visual update */
939 for (j = 0; j < i; ++j)
940 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
941 fputs(" \b", stdout);
942 for (j = 0; j < i; ++j)
943 putchar('\b');
944 NYD_LEAVE;
947 static ssize_t
948 _ncl_wboundary(struct line *l, ssize_t dir)
950 size_t c, t, i;
951 struct cell *cap;
952 bool_t anynon;
953 NYD_ENTER;
955 c = l->cursor;
956 t = l->topins;
957 i = (size_t)-1;
959 if (dir < 0) {
960 if (c == 0)
961 goto jleave;
962 } else if (c == t)
963 goto jleave;
964 else
965 --t, --c; /* Unsigned wrapping may occur (twice), then */
967 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
968 wchar_t wc = cap[c + dir].wc;
969 if (iswblank(wc) || iswpunct(wc)) {
970 if (anynon)
971 break;
972 } else
973 anynon = TRU1;
974 ++i;
975 c += dir;
976 if (dir < 0) {
977 if (c == 0)
978 break;
979 } else if (c == t)
980 break;
982 jleave:
983 NYD_LEAVE;
984 return (ssize_t)i;
987 static ssize_t
988 _ncl_cell2dat(struct line *l)
990 size_t len = 0, i;
991 NYD_ENTER;
993 if (l->topins > 0)
994 for (i = 0; i < l->topins; ++i) {
995 struct cell *cap = l->line.cells + i;
996 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
997 len += cap->count;
999 l->line.cbuf[len] = '\0';
1000 NYD_LEAVE;
1001 return (ssize_t)len;
1004 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
1005 static void
1006 _ncl_cell2save(struct line *l)
1008 size_t len, i;
1009 struct cell *cap;
1010 NYD_ENTER;
1012 l->savec.s = NULL, l->savec.l = 0;
1013 if (l->topins == 0)
1014 goto jleave;
1016 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
1017 len += cap->count;
1019 l->savec.l = len;
1020 l->savec.s = salloc(len + 1);
1022 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
1023 memcpy(l->savec.s + len, cap->cbuf, cap->count);
1024 len += cap->count;
1026 l->savec.s[len] = '\0';
1027 jleave:
1028 NYD_LEAVE;
1030 # endif
1032 static void
1033 _ncl_khome(struct line *l, bool_t dobell)
1035 size_t c;
1036 NYD_ENTER;
1038 c = l->cursor;
1039 if (c > 0) {
1040 l->cursor = 0;
1041 while (c-- != 0)
1042 putchar('\b');
1043 } else if (dobell)
1044 putchar('\a');
1045 NYD_LEAVE;
1048 static void
1049 _ncl_kend(struct line *l)
1051 ssize_t i;
1052 NYD_ENTER;
1054 i = (ssize_t)(l->topins - l->cursor);
1056 if (i > 0) {
1057 l->cursor = l->topins;
1058 while (i-- != 0)
1059 fputs(l->nd, stdout);
1060 } else
1061 putchar('\a');
1062 NYD_LEAVE;
1065 static void
1066 _ncl_kbs(struct line *l)
1068 ssize_t c, t;
1069 NYD_ENTER;
1071 c = l->cursor;
1072 t = l->topins;
1074 if (c > 0) {
1075 putchar('\b');
1076 l->cursor = --c;
1077 l->topins = --t;
1078 t -= c;
1079 _ncl_bs_eof_dvup(l->line.cells + c, t);
1080 } else
1081 putchar('\a');
1082 NYD_LEAVE;
1085 static void
1086 _ncl_kkill(struct line *l, bool_t dobell)
1088 size_t j, c, i;
1089 NYD_ENTER;
1091 c = l->cursor;
1092 i = (size_t)(l->topins - c);
1094 if (i > 0) {
1095 l->topins = c;
1096 for (j = i; j != 0; --j)
1097 putchar(' ');
1098 for (j = i; j != 0; --j)
1099 putchar('\b');
1100 } else if (dobell)
1101 putchar('\a');
1102 NYD_LEAVE;
1105 static ssize_t
1106 _ncl_keof(struct line *l)
1108 size_t c, t;
1109 ssize_t i;
1110 NYD_ENTER;
1112 c = l->cursor;
1113 t = l->topins;
1114 i = (ssize_t)(t - c);
1116 if (i > 0) {
1117 l->topins = --t;
1118 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1119 } else if (t == 0 && !ok_blook(ignoreeof)) {
1120 fputs("^D", stdout);
1121 fflush(stdout);
1122 i = -1;
1123 } else {
1124 putchar('\a');
1125 i = 0;
1127 NYD_LEAVE;
1128 return i;
1131 static void
1132 _ncl_kleft(struct line *l)
1134 NYD_ENTER;
1135 if (l->cursor > 0) {
1136 --l->cursor;
1137 putchar('\b');
1138 } else
1139 putchar('\a');
1140 NYD_LEAVE;
1143 static void
1144 _ncl_kright(struct line *l)
1146 NYD_ENTER;
1147 if (l->cursor < l->topins) {
1148 ++l->cursor;
1149 fputs(l->nd, stdout);
1150 } else
1151 putchar('\a');
1152 NYD_LEAVE;
1155 static void
1156 _ncl_krefresh(struct line *l)
1158 struct cell *cap;
1159 size_t i;
1160 NYD_ENTER;
1162 putchar('\r');
1163 if (l->prompt != NULL && *l->prompt != '\0')
1164 fputs(l->prompt, stdout);
1165 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1166 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1167 for (i = l->topins - l->cursor; i > 0; --i)
1168 putchar('\b');
1169 NYD_LEAVE;
1172 static void
1173 _ncl_kbwddelw(struct line *l)
1175 ssize_t i;
1176 size_t c, t, j;
1177 struct cell *cap;
1178 NYD_ENTER;
1180 i = _ncl_wboundary(l, -1);
1181 if (i <= 0) {
1182 if (i < 0)
1183 putchar('\a');
1184 goto jleave;
1187 c = l->cursor - i;
1188 t = l->topins;
1189 l->topins = t - i;
1190 l->cursor = c;
1191 cap = l->line.cells + c;
1193 if (t != l->cursor) {
1194 j = t - c + i;
1195 memmove(cap, cap + i, j * sizeof(*cap));
1198 for (j = i; j > 0; --j)
1199 putchar('\b');
1200 for (j = l->topins - c; j > 0; ++cap, --j)
1201 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1202 for (j = i; j > 0; --j)
1203 putchar(' ');
1204 for (j = t - c; j > 0; --j)
1205 putchar('\b');
1206 jleave:
1207 NYD_LEAVE;
1210 static void
1211 _ncl_kgow(struct line *l, ssize_t dir)
1213 ssize_t i;
1214 NYD_ENTER;
1216 i = _ncl_wboundary(l, dir);
1217 if (i <= 0) {
1218 if (i < 0)
1219 putchar('\a');
1220 goto jleave;
1223 if (dir < 0) {
1224 l->cursor -= i;
1225 while (i-- > 0)
1226 putchar('\b');
1227 } else {
1228 l->cursor += i;
1229 while (i-- > 0)
1230 fputs(l->nd, stdout);
1232 jleave:
1233 NYD_LEAVE;
1236 static void
1237 _ncl_kother(struct line *l, wchar_t wc)
1239 /* Append if at EOL, insert otherwise;
1240 * since we may move around character-wise, always use a fresh ps */
1241 mbstate_t ps;
1242 struct cell cell, *cap;
1243 size_t i, c;
1244 NYD_ENTER;
1246 /* First init a cell and see wether we'll really handle this wc */
1247 cell.wc = wc;
1248 memset(&ps, 0, sizeof ps);
1249 i = wcrtomb(cell.cbuf, wc, &ps);
1250 if (i > MB_LEN_MAX)
1251 goto jleave;
1252 cell.count = (ui_it)i;
1253 if (enc_has_state) {
1254 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1255 if (i == 1)
1257 else if (--i < MB_LEN_MAX)
1258 cell.count += (ui_it)i;
1259 else
1260 goto jleave;
1263 /* Yes, we will! Place it in the array */
1264 c = l->cursor++;
1265 i = l->topins++ - c;
1266 cap = l->line.cells + c;
1267 if (i > 0)
1268 memmove(cap + 1, cap, i * sizeof(cell));
1269 memcpy(cap, &cell, sizeof cell);
1271 /* And update visual */
1272 c = i;
1274 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1275 while ((++cap, i-- != 0));
1276 while (c-- != 0)
1277 putchar('\b');
1278 jleave:
1279 NYD_LEAVE;
1282 # ifdef HAVE_HISTORY
1283 static size_t
1284 __ncl_khist_shared(struct line *l, struct hist *hp)
1286 size_t rv;
1287 NYD_ENTER;
1289 if ((l->hist = hp) != NULL) {
1290 l->defc.s = savestrbuf(hp->dat, hp->len);
1291 rv =
1292 l->defc.l = hp->len;
1293 if (l->topins > 0) {
1294 _ncl_khome(l, FAL0);
1295 _ncl_kkill(l, FAL0);
1297 } else {
1298 putchar('\a');
1299 rv = 0;
1301 NYD_LEAVE;
1302 return rv;
1305 static size_t
1306 _ncl_khist(struct line *l, bool_t backwd)
1308 struct hist *hp;
1309 size_t rv;
1310 NYD_ENTER;
1312 /* If we're not in history mode yet, save line content;
1313 * also, disallow forward search, then, and, of course, bail unless we
1314 * do have any history at all */
1315 if ((hp = l->hist) == NULL) {
1316 if (!backwd)
1317 goto jleave;
1318 if ((hp = _ncl_hist) == NULL)
1319 goto jleave;
1320 _ncl_cell2save(l);
1321 goto jleave;
1324 hp = backwd ? hp->older : hp->younger;
1325 jleave:
1326 rv = __ncl_khist_shared(l, hp);
1327 NYD_LEAVE;
1328 return rv;
1331 static size_t
1332 _ncl_krhist(struct line *l)
1334 struct str orig_savec;
1335 struct hist *hp = NULL;
1336 size_t rv;
1337 NYD_ENTER;
1339 /* We cannot complete an empty line */
1340 if (l->topins == 0) {
1341 /* XXX The upcoming hard reset would restore a set savec buffer,
1342 * XXX so forcefully reset that. A cleaner solution would be to
1343 * XXX reset it whenever a restore is no longer desired */
1344 l->savec.s = NULL, l->savec.l = 0;
1345 goto jleave;
1347 if ((hp = l->hist) == NULL) {
1348 if ((hp = _ncl_hist) == NULL)
1349 goto jleave;
1350 orig_savec.s = NULL;
1351 orig_savec.l = 0; /* silence CC */
1352 } else if ((hp = hp->older) == NULL)
1353 goto jleave;
1354 else
1355 orig_savec = l->savec;
1357 if (orig_savec.s == NULL)
1358 _ncl_cell2save(l);
1359 for (; hp != NULL; hp = hp->older)
1360 if (is_prefix(l->savec.s, hp->dat))
1361 break;
1362 if (orig_savec.s != NULL)
1363 l->savec = orig_savec;
1364 jleave:
1365 rv = __ncl_khist_shared(l, hp);
1366 NYD_LEAVE;
1367 return rv;
1369 # endif
1371 # ifdef HAVE_TABEXPAND
1372 static size_t
1373 _ncl_kht(struct line *l)
1375 struct str orig, bot, topp, sub, exp;
1376 struct cell *cword, *ctop, *cx;
1377 bool_t set_savec = FAL0;
1378 size_t rv = 0;
1379 NYD_ENTER;
1381 /* We cannot expand an empty line */
1382 if (l->topins == 0)
1383 goto jleave;
1385 /* Get plain line data; if this is the first expansion/xy, update the
1386 * very original content so that ^G gets the origin back */
1387 orig = l->savec;
1388 _ncl_cell2save(l);
1389 exp = l->savec;
1390 if (orig.s != NULL)
1391 l->savec = orig;
1392 else
1393 set_savec = TRU1;
1394 orig = exp;
1396 cword = l->line.cells;
1397 ctop = cword + l->cursor;
1399 /* topp: separate data right of cursor */
1400 if ((cx = cword + l->topins) != ctop) {
1401 for (rv = 0; cx > ctop; --cx)
1402 rv += cx->count;
1403 topp.l = rv;
1404 topp.s = orig.s + orig.l - rv;
1405 } else
1406 topp.s = NULL, topp.l = 0;
1408 /* bot, sub: we cannot expand the entire data left of cursor, but only
1409 * the last "word", so separate them */
1410 while (cx > cword && !iswspace(cx[-1].wc))
1411 --cx;
1412 for (rv = 0; cword < cx; ++cword)
1413 rv += cword->count;
1414 sub =
1415 bot = orig;
1416 bot.l = rv;
1417 sub.s += rv;
1418 sub.l -= rv;
1419 sub.l -= topp.l;
1421 /* Leave room for "implicit asterisk" expansion, as below */
1422 if (sub.l == 0) {
1423 sub.s = UNCONST("*");
1424 sub.l = 1;
1425 } else {
1426 exp.s = salloc(sub.l + 1 +1);
1427 memcpy(exp.s, sub.s, sub.l);
1428 exp.s[sub.l] = '\0';
1429 sub.s = exp.s;
1432 /* TODO there is a TODO note upon fexpand() with multi-return;
1433 * TODO if that will change, the if() below can be simplified */
1434 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1435 jredo:
1436 hold_all_sigs();
1437 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1438 rele_all_sigs();
1440 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1441 goto jnope;
1442 /* If the expansion equals the original string, assume the user wants what
1443 * is usually known as tab completion, append `*' and restart */
1444 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1445 if (sub.s[sub.l - 1] == '*')
1446 goto jnope;
1447 sub.s[sub.l++] = '*';
1448 sub.s[sub.l] = '\0';
1449 goto jredo;
1452 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1453 * Take care to take *prompt* into account, since we don't know
1454 * anything about it's visual length (fputs(3) is used), simply
1455 * assume each character requires two columns */
1456 /* TODO the problem is that we loose control otherwise; in the best
1457 * TODO case the user can control via ^A and ^K etc., but be safe;
1458 * TODO we cannot simply adjust fexpand() because we don't know how
1459 * TODO that is implemented... The real solution would be to check
1460 * TODO wether we fit on a line, and start a pager if not.
1461 * TODO However, that should be part of a real tab-COMPLETION, then,
1462 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1463 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1464 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1465 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1466 char const e1[] = "[ERR_TOO_LONG]";
1467 exp.s = UNCONST(e1);
1468 exp.l = sizeof(e1) - 1;
1469 topp.l = 0;
1470 if (rv + bot.l + exp.l >= MAX_INPUT)
1471 bot.l = 0;
1472 if (rv + exp.l >= MAX_INPUT) {
1473 char const e2[] = "[ERR]";
1474 exp.s = UNCONST(e2);
1475 exp.l = sizeof(e2) - 1;
1479 orig.l = bot.l + exp.l + topp.l;
1480 orig.s = salloc(orig.l + 5 +1);
1481 if ((rv = bot.l) > 0)
1482 memcpy(orig.s, bot.s, rv);
1483 memcpy(orig.s + rv, exp.s, exp.l);
1484 rv += exp.l;
1485 if (topp.l > 0) {
1486 memcpy(orig.s + rv, topp.s, topp.l);
1487 rv += topp.l;
1489 orig.s[rv] = '\0';
1491 l->defc = orig;
1492 _ncl_khome(l, FAL0);
1493 _ncl_kkill(l, FAL0);
1494 jleave:
1495 NYD_LEAVE;
1496 return rv;
1497 jnope:
1498 /* If we've provided a default content, but failed to expand, there is
1499 * nothing we can "revert to": drop that default again */
1500 if (set_savec)
1501 l->savec.s = NULL, l->savec.l = 0;
1502 rv = 0;
1503 goto jleave;
1505 # endif /* HAVE_TABEXPAND */
1507 static ssize_t
1508 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1509 SMALLOC_DEBUG_ARGS)
1511 /* We want to save code, yet we may have to incorporate a lines'
1512 * default content and / or default input to switch back to after some
1513 * history movement; let "len > 0" mean "have to display some data
1514 * buffer", and only otherwise read(2) it */
1515 mbstate_t ps[2];
1516 struct line l;
1517 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp, cursor_maybe, cursor_store;
1518 wchar_t wc;
1519 ssize_t rv;
1520 NYD_ENTER;
1522 memset(&l, 0, sizeof l);
1523 l.line.cbuf = *buf;
1524 if (len != 0) {
1525 l.defc.s = savestrbuf(*buf, len);
1526 l.defc.l = len;
1528 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1529 l.prompt = prompt = "?ERR?";
1530 /* TODO *l.nd=='\0' : instead adjust accmacvar.c to disallow empty vals */
1531 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1532 l.nd = "\033[C"; /* XXX no "magic" constant */
1533 l.x_buf = buf;
1534 l.x_bufsize = bufsize;
1536 if (prompt != NULL && *prompt != '\0') {
1537 fputs(prompt, stdout);
1538 fflush(stdout);
1540 jrestart:
1541 memset(ps, 0, sizeof ps);
1542 cursor_maybe = cursor_store = 0;
1543 /* TODO: NCL: we should output the reset sequence when we jrestart:
1544 * TODO: NCL: if we are using a stateful encoding? !
1545 * TODO: NCL: in short: this is not yet well understood */
1546 for (;;) {
1547 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1549 /* Normal read(2)? Else buffer-takeover: speed this one up */
1550 if (len == 0)
1551 cbufp =
1552 cbuf = cbuf_base;
1553 else {
1554 assert(l.defc.l > 0 && l.defc.s != NULL);
1555 cbufp =
1556 cbuf = l.defc.s + (l.defc.l - len);
1557 cbufp += len;
1560 /* Read in the next complete multibyte character */
1561 for (;;) {
1562 if (len == 0) {
1563 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1564 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1565 continue;
1566 goto jleave;
1568 ++cbufp;
1571 /* Ach! the ISO C multibyte handling!
1572 * Encodings with locking shift states cannot really be helped, since
1573 * it is impossible to only query the shift state, as opposed to the
1574 * entire shift state + character pair (via ISO C functions) */
1575 rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), ps + 0);
1576 if (rv <= 0) {
1577 /* Any error during take-over can only result in a hard reset;
1578 * Otherwise, if it's a hard error, or if too many redundant shift
1579 * sequences overflow our buffer, also perform a hard reset */
1580 if (len != 0 || rv == -1 ||
1581 sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)) {
1582 l.savec.s = l.defc.s = NULL,
1583 l.savec.l = l.defc.l = len = 0;
1584 putchar('\a');
1585 wc = 'G';
1586 goto jreset;
1588 /* Otherwise, due to the way we deal with the buffer, we need to
1589 * restore the mbstate_t from before this conversion */
1590 ps[0] = ps[1];
1591 continue;
1594 if (len != 0 && (len -= (size_t)rv) == 0)
1595 l.defc.s = NULL, l.defc.l = 0;
1596 ps[1] = ps[0];
1597 break;
1600 /* Don't interpret control bytes during buffer take-over */
1601 if (cbuf != cbuf_base)
1602 goto jprint;
1603 switch (wc) {
1604 case 'A' ^ 0x40: /* cursor home */
1605 _ncl_khome(&l, TRU1);
1606 break;
1607 case 'B' ^ 0x40: /* backward character */
1608 j_b:
1609 _ncl_kleft(&l);
1610 break;
1611 /* 'C': interrupt (CTRL-C) */
1612 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1613 if ((rv = _ncl_keof(&l)) < 0)
1614 goto jleave;
1615 break;
1616 case 'E' ^ 0x40: /* end of line */
1617 _ncl_kend(&l);
1618 break;
1619 case 'F' ^ 0x40: /* forward character */
1620 j_f:
1621 _ncl_kright(&l);
1622 break;
1623 /* 'G' below */
1624 case 'H' ^ 0x40: /* backspace */
1625 case '\177':
1626 _ncl_kbs(&l);
1627 break;
1628 case 'I' ^ 0x40: /* horizontal tab */
1629 # ifdef HAVE_TABEXPAND
1630 if ((len = _ncl_kht(&l)) > 0)
1631 goto jrestart;
1632 # endif
1633 goto jbell;
1634 case 'J' ^ 0x40: /* NL (\n) */
1635 goto jdone;
1636 case 'G' ^ 0x40: /* full reset */
1637 jreset:
1638 /* FALLTHRU */
1639 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1640 _ncl_khome(&l, FAL0);
1641 /* FALLTHRU */
1642 case 'K' ^ 0x40: /* kill from cursor to end of line */
1643 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1644 /* (Handle full reset?) */
1645 if (wc == ('G' ^ 0x40)) {
1646 # ifdef HAVE_HISTORY
1647 l.hist = NULL;
1648 # endif
1649 if ((len = l.savec.l) != 0) {
1650 l.defc = l.savec;
1651 l.savec.s = NULL, l.savec.l = 0;
1652 } else
1653 len = l.defc.l;
1655 fflush(stdout);
1656 goto jrestart;
1657 case 'L' ^ 0x40: /* repaint line */
1658 j_l:
1659 _ncl_krefresh(&l);
1660 break;
1661 /* 'M': CR (\r) */
1662 case 'N' ^ 0x40: /* history next */
1663 j_n:
1664 # ifdef HAVE_HISTORY
1665 if (l.hist == NULL)
1666 goto jbell;
1667 if ((len = _ncl_khist(&l, FAL0)) > 0)
1668 goto jrestart;
1669 wc = 'G' ^ 0x40;
1670 goto jreset;
1671 # else
1672 goto jbell;
1673 # endif
1674 /* 'O' */
1675 case 'O' ^ 0x40: /* `dp' */
1676 putchar('\n');
1677 cbuf_base[0] = 'd';
1678 cbuf_base[1] = 'p';
1679 cbuf_base[2] = '\0';
1680 inhook = 0;
1681 execute(cbuf_base, TRU1, 2);
1682 goto j_l;
1683 case 'P' ^ 0x40: /* history previous */
1684 j_p:
1685 # ifdef HAVE_HISTORY
1686 if ((len = _ncl_khist(&l, TRU1)) > 0)
1687 goto jrestart;
1688 wc = 'G' ^ 0x40;
1689 goto jreset;
1690 # else
1691 goto jbell;
1692 # endif
1693 /* 'Q': no code */
1694 case 'R' ^ 0x40: /* reverse history search */
1695 # ifdef HAVE_HISTORY
1696 if ((len = _ncl_krhist(&l)) > 0)
1697 goto jrestart;
1698 wc = 'G' ^ 0x40;
1699 goto jreset;
1700 # else
1701 goto jbell;
1702 # endif
1703 /* 'S': no code */
1704 /* 'U' above */
1705 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1706 case 'W' ^ 0x40: /* backward delete "word" */
1707 _ncl_kbwddelw(&l);
1708 break;
1709 case 'X' ^ 0x40: /* move cursor forward "word" */
1710 _ncl_kgow(&l, +1);
1711 break;
1712 case 'Y' ^ 0x40: /* move cursor backward "word" */
1713 _ncl_kgow(&l, -1);
1714 break;
1715 /* 'Z': suspend (CTRL-Z) */
1716 case 0x1B:
1717 if (cursor_maybe++ != 0)
1718 goto jreset;
1719 continue;
1720 default:
1721 /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */
1722 if (cursor_maybe > 0) {
1723 if (++cursor_maybe == 2) {
1724 if (wc == L'[')
1725 continue;
1726 cursor_maybe = 0;
1727 } else if (cursor_maybe == 3) {
1728 cursor_maybe = 0;
1729 switch (wc) {
1730 default: break;
1731 case L'A': goto j_p;
1732 case L'B': goto j_n;
1733 case L'C': goto j_f;
1734 case L'D': goto j_b;
1735 case L'H':
1736 cursor_store = '0';
1737 goto J_xterm_noapp;
1738 case L'F':
1739 cursor_store = '$';
1740 goto J_xterm_noapp;
1741 case L'1':
1742 case L'4':
1743 case L'5':
1744 case L'6':
1745 cursor_store = ((wc == L'1') ? '0' :
1746 (wc == L'4' ? '$' : (wc == L'5' ? '-' : '+')));
1747 cursor_maybe = 3;
1748 continue;
1750 _ncl_kother(&l, L'[');
1751 } else {
1752 cursor_maybe = 0;
1753 if (wc == L'~')
1754 J_xterm_noapp: {
1755 char x[2];
1756 x[0] = cursor_store;
1757 x[1] = '\0';
1758 putchar('\n');
1759 c_scroll(x);
1760 cursor_store = 0;
1761 goto j_l;
1763 _ncl_kother(&l, L'[');
1764 _ncl_kother(&l, (wchar_t)cursor_store);
1765 cursor_store = 0;
1768 jprint:
1769 if (iswprint(wc)) {
1770 _ncl_kother(&l, wc);
1771 /* Don't clear the history during takeover..
1772 * ..and also avoid fflush()ing unless we've worked entire buffer */
1773 if (len > 0)
1774 continue;
1775 # ifdef HAVE_HISTORY
1776 if (cbuf == cbuf_base)
1777 l.hist = NULL;
1778 # endif
1779 } else {
1780 jbell:
1781 putchar('\a');
1783 break;
1785 fflush(stdout);
1788 /* We have a completed input line, convert the struct cell data to its
1789 * plain character equivalent */
1790 jdone:
1791 putchar('\n');
1792 fflush(stdout);
1793 len = _ncl_cell2dat(&l);
1794 rv = (ssize_t)len;
1795 jleave:
1796 NYD_LEAVE;
1797 return rv;
1800 FL void
1801 tty_init(void)
1803 # ifdef HAVE_HISTORY
1804 long hs;
1805 char *v, *lbuf;
1806 FILE *f;
1807 size_t lsize, cnt, llen;
1808 # endif
1809 NYD_ENTER;
1811 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1812 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1813 _ncl_ottou.sint = -1;
1815 # ifdef HAVE_HISTORY
1816 _CL_HISTSIZE(hs);
1817 _ncl_hist_size = 0;
1818 _ncl_hist_size_max = hs;
1819 if (hs == 0)
1820 goto jleave;
1822 _CL_HISTFILE(v);
1823 if (v == NULL)
1824 goto jleave;
1826 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1827 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1828 if (f == NULL)
1829 goto jdone;
1830 fcntl_lock(fileno(f), FLOCK_READ); /* TODO ouch, retval check, etc. */
1832 lbuf = NULL;
1833 lsize = 0;
1834 cnt = fsize(f);
1835 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1836 if (llen > 0 && lbuf[llen - 1] == '\n')
1837 lbuf[--llen] = '\0';
1838 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1839 continue;
1840 else {
1841 bool_t isgabby = (lbuf[0] == '*');
1842 _ncl_hist_load = TRU1;
1843 tty_addhist(lbuf + isgabby, isgabby);
1844 _ncl_hist_load = FAL0;
1847 if (lbuf != NULL)
1848 free(lbuf);
1850 fclose(f);
1851 jdone:
1852 rele_all_sigs(); /* XXX remove jumps */
1853 jleave:
1854 # endif /* HAVE_HISTORY */
1855 NYD_LEAVE;
1858 FL void
1859 tty_destroy(void)
1861 # ifdef HAVE_HISTORY
1862 long hs;
1863 char *v;
1864 struct hist *hp;
1865 bool_t dogabby;
1866 FILE *f;
1867 # endif
1868 NYD_ENTER;
1870 # ifdef HAVE_HISTORY
1871 _CL_HISTSIZE(hs);
1872 if (hs == 0)
1873 goto jleave;
1874 _CL_HISTFILE(v);
1875 if (v == NULL)
1876 goto jleave;
1878 if ((hp = _ncl_hist) != NULL)
1879 while (hp->older != NULL && hs-- != 0)
1880 hp = hp->older;
1881 dogabby = ok_blook(history_gabby_persist);
1883 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1884 f = fopen(v, "w"); /* TODO temporary + rename?! */
1885 if (f == NULL)
1886 goto jdone;
1887 fcntl_lock(fileno(f), FLOCK_WRITE); /* TODO ouch, retval check, etc. */
1888 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1889 goto jclose;
1891 for (; hp != NULL; hp = hp->younger) {
1892 if (!hp->isgabby || dogabby) {
1893 if (hp->isgabby)
1894 putc('*', f);
1895 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1896 putc('\n', f);
1899 jclose:
1900 fclose(f);
1901 jdone:
1902 rele_all_sigs(); /* XXX remove jumps */
1903 jleave:
1904 # endif /* HAVE_HISTORY */
1905 NYD_LEAVE;
1908 FL void
1909 tty_signal(int sig)
1911 sigset_t nset, oset;
1912 NYD_X; /* Signal handler */
1914 switch (sig) {
1915 case SIGWINCH:
1916 /* We don't deal with SIGWINCH, yet get called from main.c */
1917 break;
1918 default:
1919 _ncl_term_mode(FAL0);
1920 _ncl_sigs_down();
1921 sigemptyset(&nset);
1922 sigaddset(&nset, sig);
1923 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1924 kill(0, sig);
1925 /* When we come here we'll continue editing, so reestablish */
1926 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1927 _ncl_sigs_up();
1928 _ncl_term_mode(TRU1);
1929 break;
1933 FL int
1934 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1935 SMALLOC_DEBUG_ARGS)
1937 ssize_t nn;
1938 NYD_ENTER;
1940 /* Of course we have races here, but they cannot be avoided on POSIX
1941 * (except by even *more* actions) */
1942 _ncl_sigs_up();
1943 _ncl_term_mode(TRU1);
1944 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1945 _ncl_term_mode(FAL0);
1946 _ncl_sigs_down();
1947 NYD_LEAVE;
1948 return (int)nn;
1951 FL void
1952 tty_addhist(char const *s, bool_t isgabby)
1954 # ifdef HAVE_HISTORY
1955 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1956 ui32_t l;
1957 struct hist *h, *o, *y;
1958 # endif
1959 NYD_ENTER;
1960 UNUSED(s);
1961 UNUSED(isgabby);
1963 # ifdef HAVE_HISTORY
1964 if (isgabby && !ok_blook(history_gabby))
1965 goto j_leave;
1966 if (_ncl_hist_size_max == 0)
1967 goto j_leave;
1968 _CL_CHECK_ADDHIST(s, goto j_leave);
1970 l = (ui32_t)strlen(s);
1972 /* Eliminating duplicates is expensive, but simply inacceptable so
1973 * during the load of a potentially large history file! */
1974 if (!_ncl_hist_load)
1975 for (h = _ncl_hist; h != NULL; h = h->older)
1976 if (h->len == l && !strcmp(h->dat, s)) {
1977 hold_all_sigs(); /* TODO */
1978 if (h->isgabby)
1979 h->isgabby = !!isgabby;
1980 o = h->older;
1981 y = h->younger;
1982 if (o != NULL)
1983 o->younger = y;
1984 else
1985 _ncl_hist_tail = y;
1986 if (y != NULL)
1987 y->older = o;
1988 else
1989 _ncl_hist = o;
1990 goto jleave;
1992 hold_all_sigs();
1994 ++_ncl_hist_size;
1995 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1996 --_ncl_hist_size;
1997 if ((h = _ncl_hist_tail) != NULL) {
1998 if ((_ncl_hist_tail = h->younger) == NULL)
1999 _ncl_hist = NULL;
2000 else
2001 _ncl_hist_tail->older = NULL;
2002 free(h);
2006 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
2007 h->isgabby = !!isgabby;
2008 h->len = l;
2009 memcpy(h->dat, s, l +1);
2010 jleave:
2011 if ((h->older = _ncl_hist) != NULL)
2012 _ncl_hist->younger = h;
2013 else
2014 _ncl_hist_tail = h;
2015 h->younger = NULL;
2016 _ncl_hist = h;
2018 rele_all_sigs();
2019 j_leave:
2020 # endif
2021 NYD_LEAVE;
2024 # ifdef HAVE_HISTORY
2025 FL int
2026 c_history(void *v)
2028 C_HISTORY_SHARED;
2030 jlist: {
2031 FILE *fp;
2032 size_t i, b;
2033 struct hist *h;
2035 if (_ncl_hist == NULL)
2036 goto jleave;
2038 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
2039 NULL) {
2040 perror("tmpfile");
2041 v = NULL;
2042 goto jleave;
2045 i = _ncl_hist_size;
2046 b = 0;
2047 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
2048 fprintf(fp, "%c%4" ZFMT ". %-50.50s (%4" ZFMT "+%2u bytes)\n",
2049 (h->isgabby ? '*' : ' '), i, h->dat, b, h->len);
2051 page_or_print(fp, i);
2052 Fclose(fp);
2054 goto jleave;
2056 jclear: {
2057 struct hist *h;
2058 while ((h = _ncl_hist) != NULL) {
2059 _ncl_hist = h->older;
2060 free(h);
2062 _ncl_hist_tail = NULL;
2063 _ncl_hist_size = 0;
2065 goto jleave;
2067 jentry: {
2068 struct hist *h = _ncl_hist;
2069 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2070 entry = (long)_ncl_hist_size - entry;
2071 for (h = _ncl_hist;; h = h->older)
2072 if (h == NULL)
2073 break;
2074 else if (entry-- != 0)
2075 continue;
2076 else {
2077 v = temporary_arg_v_store = h->dat;
2078 goto jleave;
2081 v = NULL;
2083 goto jleave;
2085 # endif /* HAVE_HISTORY */
2086 #endif /* HAVE_NCL */
2089 * The really-nothing-at-all implementation
2092 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2093 FL void
2094 tty_init(void)
2096 NYD_ENTER;
2097 NYD_LEAVE;
2100 FL void
2101 tty_destroy(void)
2103 NYD_ENTER;
2104 NYD_LEAVE;
2107 FL void
2108 tty_signal(int sig)
2110 NYD_X; /* Signal handler */
2111 UNUSED(sig);
2114 FL int
2115 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2116 SMALLOC_DEBUG_ARGS)
2118 /* TODO The nothing-at-all tty layer even forces re-entering all the
2119 * TODO original data when re-editing a field */
2120 bool_t doffl = FAL0;
2121 int rv;
2122 NYD_ENTER;
2124 if (prompt != NULL && *prompt != '\0') {
2125 fputs(prompt, stdout);
2126 doffl = TRU1;
2128 if (n > 0) {
2129 fprintf(stdout, _("{former content: %.*s} "), (int)n, *linebuf);
2130 n = 0;
2131 doffl = TRU1;
2133 if (doffl)
2134 fflush(stdout);
2135 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2136 NYD_LEAVE;
2137 return rv;
2140 FL void
2141 tty_addhist(char const *s, bool_t isgabby)
2143 NYD_ENTER;
2144 UNUSED(s);
2145 UNUSED(isgabby);
2146 NYD_LEAVE;
2148 #endif /* nothing at all */
2150 /* vim:set fenc=utf-8:s-it-mode */