Don't set *dot* in -# batch mode
[s-mailx.git] / tty.c
blobbd4cf1cc386b2a1e753dbd4ca188388158792cc9
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /* The NCL version is
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 * Copyright (c) 1980, 1993
22 * The Regents of the University of California. All rights reserved.
24 * Redistribution and use in source and binary forms, with or without
25 * modification, are permitted provided that the following conditions
26 * are met:
27 * 1. Redistributions of source code must retain the above copyright
28 * notice, this list of conditions and the following disclaimer.
29 * 2. Redistributions in binary form must reproduce the above copyright
30 * notice, this list of conditions and the following disclaimer in the
31 * documentation and/or other materials provided with the distribution.
32 * 3. Neither the name of the University nor the names of its contributors
33 * may be used to endorse or promote products derived from this software
34 * without specific prior written permission.
36 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
37 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
40 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
41 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
42 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
44 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
45 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46 * SUCH DAMAGE.
48 #undef n_FILE
49 #define n_FILE tty
51 #ifndef HAVE_AMALGAMATION
52 # include "nail.h"
53 #endif
55 #ifdef HAVE_READLINE
56 # include <readline/readline.h>
57 # ifdef HAVE_HISTORY
58 # include <readline/history.h>
59 # endif
60 #elif defined HAVE_EDITLINE
61 # include <histedit.h>
62 #endif
64 /* Shared history support macros */
65 #ifdef HAVE_HISTORY
66 # define _CL_HISTFILE(S) \
67 do {\
68 char const *__hist_obsolete = ok_vlook(NAIL_HISTFILE);\
69 if (__hist_obsolete != NULL)\
70 OBSOLETE(_("please use *history-file* instead of *NAIL_HISTFILE*"));\
71 S = ok_vlook(history_file);\
72 if ((S) == NULL)\
73 (S) = __hist_obsolete;\
74 if ((S) != NULL)\
75 S = fexpand(S, FEXP_LOCAL | FEXP_NSHELL);\
76 } while(0)
78 # define _CL_HISTSIZE(V) \
79 do {\
80 char const *__hist_obsolete = ok_vlook(NAIL_HISTSIZE);\
81 char const *__sv = ok_vlook(history_size);\
82 long __rv;\
83 if (__hist_obsolete != NULL)\
84 OBSOLETE(_("please use *history-size* instead of *NAIL_HISTSIZE*"));\
85 if (__sv == NULL)\
86 __sv = __hist_obsolete;\
87 if (__sv == NULL || *__sv == '\0' || (__rv = strtol(__sv, NULL, 10)) == 0)\
88 (V) = HIST_SIZE;\
89 else if (__rv < 0)\
90 (V) = 0;\
91 else\
92 (V) = __rv;\
93 } while (0)
95 # define _CL_CHECK_ADDHIST(S,NOACT) \
96 do {\
97 switch (*(S)) {\
98 case '\0':\
99 case ' ':\
100 NOACT;\
101 default:\
102 break;\
104 } while (0)
106 # define C_HISTORY_SHARED \
107 char **argv = v;\
108 long entry;\
109 NYD_ENTER;\
111 if (*argv == NULL)\
112 goto jlist;\
113 if (argv[1] != NULL)\
114 goto jerr;\
115 if (!asccasecmp(*argv, "show"))\
116 goto jlist;\
117 if (!asccasecmp(*argv, "clear"))\
118 goto jclear;\
119 if ((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
120 goto jentry;\
121 jerr:\
122 n_err(_("Synopsis: history: %s\n" \
123 "<show> (default), <clear> or select <NO> from editor history"));\
124 v = NULL;\
125 jleave:\
126 NYD_LEAVE;\
127 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
128 #endif /* HAVE_HISTORY */
130 /* fexpand() flags for expand-on-tab */
131 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
134 * Because we have multiple identical implementations, change file layout a bit
135 * and place the implementations one after the other below the other externals
138 static sigjmp_buf __n_tty_actjmp; /* TODO someday, we won't need it no more */
139 static void
140 __n_tty_acthdl(int s) /* TODO someday, we won't need it no more */
142 NYD_X; /* Signal handler */
143 termios_state_reset();
144 siglongjmp(__n_tty_actjmp, s);
147 FL bool_t
148 getapproval(char const * volatile prompt, bool_t noninteract_default)
150 sighandler_type volatile oint, ohup;
151 bool_t volatile rv;
152 int volatile sig;
153 NYD_ENTER;
155 if (!(options & OPT_INTERACTIVE)) {
156 sig = 0;
157 rv = noninteract_default;
158 goto jleave;
160 rv = FAL0;
162 if (prompt == NULL)
163 prompt = noninteract_default ? _(" ([yes]/no)? ") : _(" ([no]/yes)? ");
165 oint = safe_signal(SIGINT, SIG_IGN);
166 ohup = safe_signal(SIGHUP, SIG_IGN);
167 if ((sig = sigsetjmp(__n_tty_actjmp, 1)) != 0)
168 goto jrestore;
169 safe_signal(SIGINT, &__n_tty_acthdl);
170 safe_signal(SIGHUP, &__n_tty_acthdl);
172 if (readline_input(prompt, FAL0, &termios_state.ts_linebuf,
173 &termios_state.ts_linesize, NULL) >= 0)
174 rv = (boolify(termios_state.ts_linebuf, UIZ_MAX,
175 noninteract_default) > 0);
176 jrestore:
177 termios_state_reset();
179 safe_signal(SIGHUP, ohup);
180 safe_signal(SIGINT, oint);
181 jleave:
182 NYD_LEAVE;
183 if (sig != 0)
184 n_raise(sig);
185 return rv;
188 #ifdef HAVE_SOCKETS
189 FL char *
190 getuser(char const * volatile query) /* TODO v15-compat obsolete */
192 sighandler_type volatile oint, ohup;
193 char * volatile user = NULL;
194 int volatile sig;
195 NYD_ENTER;
197 if (query == NULL)
198 query = _("User: ");
200 oint = safe_signal(SIGINT, SIG_IGN);
201 ohup = safe_signal(SIGHUP, SIG_IGN);
202 if ((sig = sigsetjmp(__n_tty_actjmp, 1)) != 0)
203 goto jrestore;
204 safe_signal(SIGINT, &__n_tty_acthdl);
205 safe_signal(SIGHUP, &__n_tty_acthdl);
207 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
208 &termios_state.ts_linesize, NULL) >= 0)
209 user = termios_state.ts_linebuf;
210 jrestore:
211 termios_state_reset();
213 safe_signal(SIGHUP, ohup);
214 safe_signal(SIGINT, oint);
215 NYD_LEAVE;
216 if (sig != 0)
217 n_raise(sig);
218 return user;
221 FL char *
222 getpassword(char const *query)
224 sighandler_type volatile oint, ohup;
225 struct termios tios;
226 char * volatile pass = NULL;
227 int volatile sig;
228 NYD_ENTER;
230 if (query == NULL)
231 query = _("Password: ");
232 fputs(query, stdout);
233 fflush(stdout);
235 /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
236 * FIXME foreground pgrp, and can fail with EINTR!! also affects
237 * FIXME termios_state_reset() */
238 if (options & OPT_TTYIN) {
239 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
240 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
241 termios_state.ts_needs_reset = TRU1;
242 tios.c_iflag &= ~(ISTRIP);
243 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
246 oint = safe_signal(SIGINT, SIG_IGN);
247 ohup = safe_signal(SIGHUP, SIG_IGN);
248 if ((sig = sigsetjmp(__n_tty_actjmp, 1)) != 0)
249 goto jrestore;
250 safe_signal(SIGINT, &__n_tty_acthdl);
251 safe_signal(SIGHUP, &__n_tty_acthdl);
253 if (options & OPT_TTYIN)
254 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
256 if (readline_restart(stdin, &termios_state.ts_linebuf,
257 &termios_state.ts_linesize, 0) >= 0)
258 pass = termios_state.ts_linebuf;
259 jrestore:
260 termios_state_reset();
261 if (options & OPT_TTYIN)
262 putc('\n', stdout);
264 safe_signal(SIGHUP, ohup);
265 safe_signal(SIGINT, oint);
266 NYD_LEAVE;
267 if (sig != 0)
268 n_raise(sig);
269 return pass;
271 #endif /* HAVE_SOCKETS */
274 * readline(3)
277 #ifdef HAVE_READLINE
278 static sighandler_type _rl_shup;
279 static char * _rl_buf; /* pre_input() hook: initial line */
280 static int _rl_buflen; /* content, and its length */
282 static int _rl_pre_input(void);
284 static int
285 _rl_pre_input(void)
287 NYD_ENTER;
288 /* Handle leftover data from \ escaped former line */
289 rl_extend_line_buffer(_rl_buflen + 10);
290 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
291 rl_point = rl_end = _rl_buflen;
292 rl_pre_input_hook = (rl_hook_func_t*)NULL;
293 rl_redisplay();
294 NYD_LEAVE;
295 return 0;
298 FL void
299 n_tty_init(void)
301 # ifdef HAVE_HISTORY
302 long hs;
303 char const *v;
304 # endif
305 NYD_ENTER;
307 rl_readline_name = UNCONST(uagent);
308 # ifdef HAVE_HISTORY
309 _CL_HISTSIZE(hs);
310 using_history();
311 stifle_history((int)hs);
312 # endif
313 rl_read_init_file(NULL);
315 /* Because rl_read_init_file() may have introduced yet a different
316 * history size limit, simply load and incorporate the history, leave
317 * it up to readline(3) to do the rest */
318 # ifdef HAVE_HISTORY
319 _CL_HISTFILE(v);
320 if (v != NULL)
321 read_history(v);
322 # endif
323 NYD_LEAVE;
326 FL void
327 n_tty_destroy(void)
329 # ifdef HAVE_HISTORY
330 char const *v;
331 # endif
332 NYD_ENTER;
334 # ifdef HAVE_HISTORY
335 _CL_HISTFILE(v);
336 if (v != NULL)
337 write_history(v);
338 # endif
339 NYD_LEAVE;
342 FL void
343 n_tty_signal(int sig)
345 sigset_t nset, oset;
346 NYD_X; /* Signal handler */
348 switch (sig) {
349 # ifdef SIGWINCH
350 case SIGWINCH:
351 break;
352 # endif
353 case SIGHUP:
354 /* readline(3) doesn't catch it :( */
355 rl_free_line_state();
356 rl_cleanup_after_signal();
357 safe_signal(SIGHUP, _rl_shup);
358 sigemptyset(&nset);
359 sigaddset(&nset, sig);
360 sigprocmask(SIG_UNBLOCK, &nset, &oset);
361 n_raise(sig);
362 /* XXX When we come here we'll continue editing, so reestablish
363 * XXX cannot happen */
364 sigprocmask(SIG_BLOCK, &oset, NULL);
365 _rl_shup = safe_signal(SIGHUP, &n_tty_signal);
366 rl_reset_after_signal();
367 break;
368 default:
369 break;
373 FL int
374 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
375 SMALLOC_DEBUG_ARGS)
377 int nn;
378 char *line;
379 NYD_ENTER;
381 if (n > 0) {
382 _rl_buf = *linebuf;
383 _rl_buflen = (int)n;
384 rl_pre_input_hook = &_rl_pre_input;
387 _rl_shup = safe_signal(SIGHUP, &n_tty_signal);
388 line = readline(prompt != NULL ? prompt : "");
389 safe_signal(SIGHUP, _rl_shup);
391 if (line == NULL) {
392 nn = -1;
393 goto jleave;
395 n = strlen(line);
397 if (n >= *linesize) {
398 *linesize = LINESIZE + n +1;
399 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
401 memcpy(*linebuf, line, n);
402 (free)(line);
403 (*linebuf)[n] = '\0';
404 nn = (int)n;
405 jleave:
406 NYD_LEAVE;
407 return nn;
410 FL void
411 n_tty_addhist(char const *s, bool_t isgabby)
413 NYD_ENTER;
414 UNUSED(s);
415 UNUSED(isgabby);
416 # ifdef HAVE_HISTORY
417 if (isgabby && !ok_blook(history_gabby))
418 goto jleave;
419 _CL_CHECK_ADDHIST(s, goto jleave);
420 hold_all_sigs(); /* XXX too heavy */
421 add_history(s); /* XXX yet we jump away! */
422 rele_all_sigs(); /* XXX remove jumps */
423 jleave:
424 # endif
425 NYD_LEAVE;
428 # ifdef HAVE_HISTORY
429 FL int
430 c_history(void *v)
432 C_HISTORY_SHARED;
434 jlist: {
435 FILE *fp;
436 HISTORY_STATE *hs;
437 HIST_ENTRY **hl;
438 ul_i i, b;
440 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
441 n_perr(_("tmpfile"), 0);
442 v = NULL;
443 goto jleave;
446 hs = history_get_history_state();
448 for (i = (ul_i)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
449 char *cp = (*--hl)->line;
450 size_t sl = strlen(cp);
451 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n", i, cp, b, sl);
452 b += sl;
455 page_or_print(fp, (size_t)hs->length);
456 Fclose(fp);
458 goto jleave;
460 jclear:
461 clear_history();
462 goto jleave;
464 jentry: {
465 HISTORY_STATE *hs = history_get_history_state();
467 if (UICMP(z, entry, <=, hs->length))
468 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
469 else
470 v = NULL;
472 goto jleave;
474 # endif /* HAVE_HISTORY */
475 #endif /* HAVE_READLINE */
478 * BSD editline(3)
481 #ifdef HAVE_EDITLINE
482 static EditLine * _el_el; /* editline(3) handle */
483 static char const * _el_prompt; /* Current prompt */
484 # ifdef HAVE_HISTORY
485 static History * _el_hcom; /* History handle for commline */
486 # endif
488 static char const * _el_getprompt(void);
490 static char const *
491 _el_getprompt(void)
493 return _el_prompt;
496 FL void
497 n_tty_init(void)
499 # ifdef HAVE_HISTORY
500 HistEvent he;
501 long hs;
502 char const *v;
503 # endif
504 NYD_ENTER;
506 # ifdef HAVE_HISTORY
507 _CL_HISTSIZE(hs);
508 _el_hcom = history_init();
509 history(_el_hcom, &he, H_SETSIZE, (int)hs);
510 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
511 # endif
513 _el_el = el_init(uagent, stdin, stdout, stderr);
514 el_set(_el_el, EL_SIGNAL, 1);
515 el_set(_el_el, EL_TERMINAL, NULL);
516 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
517 # ifdef HAVE_HISTORY
518 el_set(_el_el, EL_HIST, &history, _el_hcom);
519 # endif
520 el_set(_el_el, EL_EDITOR, "emacs");
521 # ifdef EL_PROMPT_ESC
522 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
523 # else
524 el_set(_el_el, EL_PROMPT, &_el_getprompt);
525 # endif
526 # if 0
527 el_set(_el_el, EL_ADDFN, "tab_complete",
528 "editline(3) internal completion function", &_el_file_cpl);
529 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
530 # endif
531 # ifdef HAVE_HISTORY
532 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
533 # endif
534 el_source(_el_el, NULL); /* Source ~/.editrc */
536 NYD;
537 /* Because el_source() may have introduced yet a different history size
538 * limit, simply load and incorporate the history, leave it up to
539 * editline(3) to do the rest */
540 # ifdef HAVE_HISTORY
541 _CL_HISTFILE(v);
542 if (v != NULL)
543 history(_el_hcom, &he, H_LOAD, v);
544 # endif
545 NYD;
546 NYD_LEAVE;
549 FL void
550 n_tty_destroy(void)
552 # ifdef HAVE_HISTORY
553 HistEvent he;
554 char const *v;
555 # endif
556 NYD_ENTER;
558 el_end(_el_el);
560 # ifdef HAVE_HISTORY
561 _CL_HISTFILE(v);
562 if (v != NULL)
563 history(_el_hcom, &he, H_SAVE, v);
564 history_end(_el_hcom);
565 # endif
566 NYD_LEAVE;
569 FL void
570 n_tty_signal(int sig)
572 NYD_X; /* Signal handler */
573 switch (sig) {
574 # ifdef SIGWINCH
575 case SIGWINCH:
576 el_resize(_el_el);
577 break;
578 # endif
579 default:
580 break;
584 FL int
585 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
586 SMALLOC_DEBUG_ARGS)
588 int nn;
589 char const *line;
590 NYD_ENTER;
592 _el_prompt = (prompt != NULL) ? prompt : "";
593 if (n > 0)
594 el_push(_el_el, *linebuf);
595 line = el_gets(_el_el, &nn);
597 if (line == NULL) {
598 nn = -1;
599 goto jleave;
601 assert(nn >= 0);
602 n = (size_t)nn;
603 if (n > 0 && line[n - 1] == '\n')
604 nn = (int)--n;
606 if (n >= *linesize) {
607 *linesize = LINESIZE + n + 1;
608 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
610 memcpy(*linebuf, line, n);
611 (*linebuf)[n] = '\0';
612 jleave:
613 NYD_LEAVE;
614 return nn;
617 FL void
618 n_tty_addhist(char const *s, bool_t isgabby)
620 # ifdef HAVE_HISTORY
621 /* Enlarge meaning of unique .. to something that rocks;
622 * xxx unfortunately this is expensive to do with editline(3)
623 * xxx maybe it would be better to hook the ptfs instead? */
624 HistEvent he;
625 int i;
626 # endif
627 NYD_ENTER;
628 UNUSED(s);
629 UNUSED(isgabby);
631 # ifdef HAVE_HISTORY
632 if (isgabby && !ok_blook(history_gabby))
633 goto jleave;
634 _CL_CHECK_ADDHIST(s, goto jleave);
636 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
637 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
638 i = history(_el_hcom, &he, H_NEXT))
639 if (!strcmp(he.str, s)) {
640 history(_el_hcom, &he, H_DEL, he.num);
641 break;
643 history(_el_hcom, &he, H_ENTER, s);
644 rele_all_sigs(); /* XXX remove jumps */
645 jleave:
646 # endif
647 NYD_LEAVE;
650 # ifdef HAVE_HISTORY
651 FL int
652 c_history(void *v)
654 C_HISTORY_SHARED;
656 jlist: {
657 HistEvent he;
658 FILE *fp;
659 size_t i, b;
660 int x;
662 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
663 n_perr(_("tmpfile"), 0);
664 v = NULL;
665 goto jleave;
668 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
669 b = 0;
670 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
671 x = history(_el_hcom, &he, H_NEXT)) {
672 size_t sl = strlen(he.str);
673 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n",
674 (ul_i)i, he.str, (ul_i)b, (ul_i)sl);
675 --i;
676 b += sl;
679 page_or_print(fp, i);
680 Fclose(fp);
682 goto jleave;
684 jclear: {
685 HistEvent he;
686 history(_el_hcom, &he, H_CLEAR);
688 goto jleave;
690 jentry: {
691 HistEvent he;
692 size_t i;
693 int x;
695 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
696 if (UICMP(z, entry, <=, i)) {
697 entry = (long)i - entry;
698 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
699 x = history(_el_hcom, &he, H_NEXT))
700 if (entry-- == 0) {
701 v = temporary_arg_v_store = UNCONST(he.str);
702 goto jleave;
705 v = NULL;
707 goto jleave;
709 # endif /* HAVE_HISTORY */
710 #endif /* HAVE_EDITLINE */
713 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
715 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
716 * We do not handle character widths because the terminal must deal with that
717 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
718 * characters by definition on the other. We're addicted.
720 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
721 * we're forced to use the very same buffer--the one that is passed through to
722 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
723 * convert that on-the-fly back to the plain char* result once we're done.
724 * To simplify our live, use savestr() buffers for all other needed memory
727 #ifdef HAVE_NCL
728 # ifndef MAX_INPUT
729 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
730 # endif
732 /* Since we simply fputs(3) the prompt, assume each character requires two
733 * visual cells -- and we need to restrict the maximum prompt size because
734 * of MAX_INPUT and our desire to have room for some error message left */
735 # define _PROMPT_VLEN(P) (strlen(P) * 2)
736 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
738 struct xtios {
739 struct termios told;
740 struct termios tnew;
743 struct cell {
744 wchar_t wc;
745 ui32_t count;
746 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
749 struct line {
750 size_t cursor; /* Current cursor position */
751 size_t topins; /* Outermost cursor col set */
752 union {
753 char *cbuf; /* *x_buf */
754 struct cell *cells;
755 } line;
756 struct str defc; /* Current default content */
757 struct str savec; /* Saved default content */
758 # ifdef HAVE_HISTORY
759 struct hist *hist; /* History cursor */
760 # endif
761 char const *prompt;
762 char const *nd; /* Cursor right */
763 char **x_buf; /* Caller pointers */
764 size_t *x_bufsize;
767 # ifdef HAVE_HISTORY
768 struct hist {
769 struct hist *older;
770 struct hist *younger;
771 ui32_t isgabby : 1;
772 ui32_t len : 31;
773 char dat[VFIELD_SIZE(sizeof(ui32_t))];
775 # endif
777 static sighandler_type _ncl_oint;
778 static sighandler_type _ncl_oquit;
779 static sighandler_type _ncl_oterm;
780 static sighandler_type _ncl_ohup;
781 static sighandler_type _ncl_otstp;
782 static sighandler_type _ncl_ottin;
783 static sighandler_type _ncl_ottou;
784 static struct xtios _ncl_tios;
785 # ifdef HAVE_HISTORY
786 static struct hist *_ncl_hist;
787 static struct hist *_ncl_hist_tail;
788 static size_t _ncl_hist_size;
789 static size_t _ncl_hist_size_max;
790 static bool_t _ncl_hist_load;
791 # endif
793 static void _ncl_sigs_up(void);
794 static void _ncl_sigs_down(void);
796 static void _ncl_term_mode(bool_t raw);
798 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
799 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
800 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
801 static ssize_t _ncl_cell2dat(struct line *l);
802 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
803 static void _ncl_cell2save(struct line *l);
804 # endif
806 static void _ncl_khome(struct line *l, bool_t dobell);
807 static void _ncl_kend(struct line *l);
808 static void _ncl_kbs(struct line *l);
809 static void _ncl_kkill(struct line *l, bool_t dobell);
810 static ssize_t _ncl_keof(struct line *l);
811 static void _ncl_kleft(struct line *l);
812 static void _ncl_kright(struct line *l);
813 static void _ncl_krefresh(struct line *l);
814 static void _ncl_kbwddelw(struct line *l);
815 static void _ncl_kgow(struct line *l, ssize_t dir);
816 static void _ncl_kother(struct line *l, wchar_t wc);
817 # ifdef HAVE_HISTORY
818 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
819 static size_t _ncl_khist(struct line *l, bool_t backwd);
820 static size_t _ncl_krhist(struct line *l);
821 # endif
822 # ifdef HAVE_TABEXPAND
823 static size_t _ncl_kht(struct line *l);
824 # endif
825 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
826 size_t len SMALLOC_DEBUG_ARGS);
828 static void
829 _ncl_sigs_up(void)
831 sigset_t nset, oset;
832 NYD2_ENTER;
834 sigfillset(&nset);
836 sigprocmask(SIG_BLOCK, &nset, &oset);
837 _ncl_oint = safe_signal(SIGINT, &n_tty_signal);
838 _ncl_oquit = safe_signal(SIGQUIT, &n_tty_signal);
839 _ncl_oterm = safe_signal(SIGTERM, &n_tty_signal);
840 _ncl_ohup = safe_signal(SIGHUP, &n_tty_signal);
841 _ncl_otstp = safe_signal(SIGTSTP, &n_tty_signal);
842 _ncl_ottin = safe_signal(SIGTTIN, &n_tty_signal);
843 _ncl_ottou = safe_signal(SIGTTOU, &n_tty_signal);
844 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
845 NYD2_LEAVE;
848 static void
849 _ncl_sigs_down(void)
851 sigset_t nset, oset;
852 NYD2_ENTER;
854 sigfillset(&nset);
856 sigprocmask(SIG_BLOCK, &nset, &oset);
857 safe_signal(SIGINT, _ncl_oint);
858 safe_signal(SIGQUIT, _ncl_oquit);
859 safe_signal(SIGTERM, _ncl_oterm);
860 safe_signal(SIGHUP, _ncl_ohup);
861 safe_signal(SIGTSTP, _ncl_otstp);
862 safe_signal(SIGTTIN, _ncl_ottin);
863 safe_signal(SIGTTOU, _ncl_ottou);
864 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
865 NYD2_LEAVE;
868 static void
869 _ncl_term_mode(bool_t raw)
871 struct termios *tiosp;
872 NYD2_ENTER;
874 tiosp = &_ncl_tios.told;
875 if (!raw)
876 goto jleave;
878 /* Always requery the attributes, in case we've been moved from background
879 * to foreground or however else in between sessions */
880 /* XXX Always enforce ECHO and ICANON in the OLD attributes - do so as long
881 * XXX as we don't properly deal with TTIN and TTOU etc. */
882 tcgetattr(STDIN_FILENO, tiosp);
883 tiosp->c_lflag |= ECHO | ICANON;
885 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
886 tiosp = &_ncl_tios.tnew;
887 tiosp->c_cc[VMIN] = 1;
888 tiosp->c_cc[VTIME] = 0;
889 tiosp->c_iflag &= ~(ISTRIP);
890 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
891 jleave:
892 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
893 NYD2_LEAVE;
896 static void
897 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
899 size_t i;
900 NYD2_ENTER;
902 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
903 if (i >= *l->x_bufsize) {
904 i <<= 1;
905 *l->x_bufsize = i;
906 l->line.cbuf =
907 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
909 NYD2_LEAVE;
912 static void
913 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
915 size_t j;
916 NYD2_ENTER;
918 if (i > 0)
919 memmove(cap, cap + 1, i * sizeof(*cap));
921 /* And.. the (rest of the) visual update */
922 for (j = 0; j < i; ++j)
923 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
924 fputs(" \b", stdout);
925 for (j = 0; j < i; ++j)
926 putchar('\b');
927 NYD2_LEAVE;
930 static ssize_t
931 _ncl_wboundary(struct line *l, ssize_t dir)
933 size_t c, t, i;
934 struct cell *cap;
935 bool_t anynon;
936 NYD2_ENTER;
938 c = l->cursor;
939 t = l->topins;
940 i = (size_t)-1;
942 if (dir < 0) {
943 if (c == 0)
944 goto jleave;
945 } else if (c == t)
946 goto jleave;
947 else
948 --t, --c; /* Unsigned wrapping may occur (twice), then */
950 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
951 wchar_t wc = cap[c + dir].wc;
952 if (iswblank(wc) || iswpunct(wc)) {
953 if (anynon)
954 break;
955 } else
956 anynon = TRU1;
957 ++i;
958 c += dir;
959 if (dir < 0) {
960 if (c == 0)
961 break;
962 } else if (c == t)
963 break;
965 jleave:
966 NYD2_LEAVE;
967 return (ssize_t)i;
970 static ssize_t
971 _ncl_cell2dat(struct line *l)
973 size_t len = 0, i;
974 NYD2_ENTER;
976 if (l->topins > 0)
977 for (i = 0; i < l->topins; ++i) {
978 struct cell *cap = l->line.cells + i;
979 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
980 len += cap->count;
982 l->line.cbuf[len] = '\0';
983 NYD2_LEAVE;
984 return (ssize_t)len;
987 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
988 static void
989 _ncl_cell2save(struct line *l)
991 size_t len, i;
992 struct cell *cap;
993 NYD2_ENTER;
995 l->savec.s = NULL, l->savec.l = 0;
996 if (l->topins == 0)
997 goto jleave;
999 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
1000 len += cap->count;
1002 l->savec.l = len;
1003 l->savec.s = salloc(len + 1);
1005 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
1006 memcpy(l->savec.s + len, cap->cbuf, cap->count);
1007 len += cap->count;
1009 l->savec.s[len] = '\0';
1010 jleave:
1011 NYD2_LEAVE;
1013 # endif
1015 static void
1016 _ncl_khome(struct line *l, bool_t dobell)
1018 size_t c;
1019 NYD2_ENTER;
1021 c = l->cursor;
1022 if (c > 0) {
1023 l->cursor = 0;
1024 while (c-- != 0)
1025 putchar('\b');
1026 } else if (dobell)
1027 putchar('\a');
1028 NYD2_LEAVE;
1031 static void
1032 _ncl_kend(struct line *l)
1034 ssize_t i;
1035 NYD2_ENTER;
1037 i = (ssize_t)(l->topins - l->cursor);
1039 if (i > 0) {
1040 l->cursor = l->topins;
1041 while (i-- != 0)
1042 fputs(l->nd, stdout);
1043 } else
1044 putchar('\a');
1045 NYD2_LEAVE;
1048 static void
1049 _ncl_kbs(struct line *l)
1051 ssize_t c, t;
1052 NYD2_ENTER;
1054 c = l->cursor;
1055 t = l->topins;
1057 if (c > 0) {
1058 putchar('\b');
1059 l->cursor = --c;
1060 l->topins = --t;
1061 t -= c;
1062 _ncl_bs_eof_dvup(l->line.cells + c, t);
1063 } else
1064 putchar('\a');
1065 NYD2_LEAVE;
1068 static void
1069 _ncl_kkill(struct line *l, bool_t dobell)
1071 size_t j, c, i;
1072 NYD2_ENTER;
1074 c = l->cursor;
1075 i = (size_t)(l->topins - c);
1077 if (i > 0) {
1078 l->topins = c;
1079 for (j = i; j != 0; --j)
1080 putchar(' ');
1081 for (j = i; j != 0; --j)
1082 putchar('\b');
1083 } else if (dobell)
1084 putchar('\a');
1085 NYD2_LEAVE;
1088 static ssize_t
1089 _ncl_keof(struct line *l)
1091 size_t c, t;
1092 ssize_t i;
1093 NYD2_ENTER;
1095 c = l->cursor;
1096 t = l->topins;
1097 i = (ssize_t)(t - c);
1099 if (i > 0) {
1100 l->topins = --t;
1101 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1102 } else if (t == 0 /*&& !ok_blook(ignoreeof)*/) {
1103 /*fputs("^D", stdout);
1104 fflush(stdout);*/
1105 i = -1;
1106 /*} else {
1107 putchar('\a');
1108 i = 0;*/
1110 NYD2_LEAVE;
1111 return i;
1114 static void
1115 _ncl_kleft(struct line *l)
1117 NYD2_ENTER;
1118 if (l->cursor > 0) {
1119 --l->cursor;
1120 putchar('\b');
1121 } else
1122 putchar('\a');
1123 NYD2_LEAVE;
1126 static void
1127 _ncl_kright(struct line *l)
1129 NYD2_ENTER;
1130 if (l->cursor < l->topins) {
1131 ++l->cursor;
1132 fputs(l->nd, stdout);
1133 } else
1134 putchar('\a');
1135 NYD2_LEAVE;
1138 static void
1139 _ncl_krefresh(struct line *l)
1141 struct cell *cap;
1142 size_t i;
1143 NYD2_ENTER;
1145 putchar('\r');
1146 if (l->prompt != NULL && *l->prompt != '\0')
1147 fputs(l->prompt, stdout);
1148 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1149 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1150 for (i = l->topins - l->cursor; i > 0; --i)
1151 putchar('\b');
1152 NYD2_LEAVE;
1155 static void
1156 _ncl_kbwddelw(struct line *l)
1158 ssize_t i;
1159 size_t c, t, j;
1160 struct cell *cap;
1161 NYD2_ENTER;
1163 i = _ncl_wboundary(l, -1);
1164 if (i <= 0) {
1165 if (i < 0)
1166 putchar('\a');
1167 goto jleave;
1170 c = l->cursor - i;
1171 t = l->topins;
1172 l->topins = t - i;
1173 l->cursor = c;
1174 cap = l->line.cells + c;
1176 if (t != l->cursor) {
1177 j = t - c + i;
1178 memmove(cap, cap + i, j * sizeof(*cap));
1181 for (j = i; j > 0; --j)
1182 putchar('\b');
1183 for (j = l->topins - c; j > 0; ++cap, --j)
1184 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1185 for (j = i; j > 0; --j)
1186 putchar(' ');
1187 for (j = t - c; j > 0; --j)
1188 putchar('\b');
1189 jleave:
1190 NYD2_LEAVE;
1193 static void
1194 _ncl_kgow(struct line *l, ssize_t dir)
1196 ssize_t i;
1197 NYD2_ENTER;
1199 i = _ncl_wboundary(l, dir);
1200 if (i <= 0) {
1201 if (i < 0)
1202 putchar('\a');
1203 goto jleave;
1206 if (dir < 0) {
1207 l->cursor -= i;
1208 while (i-- > 0)
1209 putchar('\b');
1210 } else {
1211 l->cursor += i;
1212 while (i-- > 0)
1213 fputs(l->nd, stdout);
1215 jleave:
1216 NYD2_LEAVE;
1219 static void
1220 _ncl_kother(struct line *l, wchar_t wc)
1222 /* Append if at EOL, insert otherwise;
1223 * since we may move around character-wise, always use a fresh ps */
1224 mbstate_t ps;
1225 struct cell cell, *cap;
1226 size_t i, c;
1227 NYD2_ENTER;
1229 /* First init a cell and see wether we'll really handle this wc */
1230 cell.wc = wc;
1231 memset(&ps, 0, sizeof ps);
1232 i = wcrtomb(cell.cbuf, wc, &ps);
1233 if (i > MB_LEN_MAX)
1234 goto jleave;
1235 cell.count = (ui32_t)i;
1236 if (options & OPT_ENC_MBSTATE) {
1237 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1238 if (i == 1)
1240 else if (--i < MB_LEN_MAX)
1241 cell.count += (ui32_t)i;
1242 else
1243 goto jleave;
1246 /* Yes, we will! Place it in the array */
1247 c = l->cursor++;
1248 i = l->topins++ - c;
1249 cap = l->line.cells + c;
1250 if (i > 0)
1251 memmove(cap + 1, cap, i * sizeof(cell));
1252 memcpy(cap, &cell, sizeof cell);
1254 /* And update visual */
1255 c = i;
1257 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1258 while ((++cap, i-- != 0));
1259 while (c-- != 0)
1260 putchar('\b');
1261 jleave:
1262 NYD2_LEAVE;
1265 # ifdef HAVE_HISTORY
1266 static size_t
1267 __ncl_khist_shared(struct line *l, struct hist *hp)
1269 size_t rv;
1270 NYD2_ENTER;
1272 if ((l->hist = hp) != NULL) {
1273 l->defc.s = savestrbuf(hp->dat, hp->len);
1274 rv =
1275 l->defc.l = hp->len;
1276 if (l->topins > 0) {
1277 _ncl_khome(l, FAL0);
1278 _ncl_kkill(l, FAL0);
1280 } else {
1281 putchar('\a');
1282 rv = 0;
1284 NYD2_LEAVE;
1285 return rv;
1288 static size_t
1289 _ncl_khist(struct line *l, bool_t backwd)
1291 struct hist *hp;
1292 size_t rv;
1293 NYD2_ENTER;
1295 /* If we're not in history mode yet, save line content;
1296 * also, disallow forward search, then, and, of course, bail unless we
1297 * do have any history at all */
1298 if ((hp = l->hist) == NULL) {
1299 if (!backwd)
1300 goto jleave;
1301 if ((hp = _ncl_hist) == NULL)
1302 goto jleave;
1303 _ncl_cell2save(l);
1304 goto jleave;
1307 hp = backwd ? hp->older : hp->younger;
1308 jleave:
1309 rv = __ncl_khist_shared(l, hp);
1310 NYD2_LEAVE;
1311 return rv;
1314 static size_t
1315 _ncl_krhist(struct line *l)
1317 struct str orig_savec;
1318 struct hist *hp = NULL;
1319 size_t rv;
1320 NYD2_ENTER;
1322 /* We cannot complete an empty line */
1323 if (l->topins == 0) {
1324 /* XXX The upcoming hard reset would restore a set savec buffer,
1325 * XXX so forcefully reset that. A cleaner solution would be to
1326 * XXX reset it whenever a restore is no longer desired */
1327 l->savec.s = NULL, l->savec.l = 0;
1328 goto jleave;
1330 if ((hp = l->hist) == NULL) {
1331 if ((hp = _ncl_hist) == NULL)
1332 goto jleave;
1333 orig_savec.s = NULL;
1334 orig_savec.l = 0; /* silence CC */
1335 } else if ((hp = hp->older) == NULL)
1336 goto jleave;
1337 else
1338 orig_savec = l->savec;
1340 if (orig_savec.s == NULL)
1341 _ncl_cell2save(l);
1342 for (; hp != NULL; hp = hp->older)
1343 if (is_prefix(l->savec.s, hp->dat))
1344 break;
1345 if (orig_savec.s != NULL)
1346 l->savec = orig_savec;
1347 jleave:
1348 rv = __ncl_khist_shared(l, hp);
1349 NYD2_LEAVE;
1350 return rv;
1352 # endif
1354 # ifdef HAVE_TABEXPAND
1355 static size_t
1356 _ncl_kht(struct line *l)
1358 struct str orig, bot, topp, sub, exp;
1359 struct cell *cword, *ctop, *cx;
1360 bool_t set_savec = FAL0;
1361 size_t rv = 0;
1362 NYD2_ENTER;
1364 /* We cannot expand an empty line */
1365 if (l->topins == 0)
1366 goto jleave;
1368 /* Get plain line data; if this is the first expansion/xy, update the
1369 * very original content so that ^G gets the origin back */
1370 orig = l->savec;
1371 _ncl_cell2save(l);
1372 exp = l->savec;
1373 if (orig.s != NULL)
1374 l->savec = orig;
1375 else
1376 set_savec = TRU1;
1377 orig = exp;
1379 cword = l->line.cells;
1380 ctop = cword + l->cursor;
1382 /* topp: separate data right of cursor */
1383 if ((cx = cword + l->topins) != ctop) {
1384 for (rv = 0; cx > ctop; --cx)
1385 rv += cx->count;
1386 topp.l = rv;
1387 topp.s = orig.s + orig.l - rv;
1388 } else
1389 topp.s = NULL, topp.l = 0;
1391 /* bot, sub: we cannot expand the entire data left of cursor, but only
1392 * the last "word", so separate them */
1393 while (cx > cword && !iswspace(cx[-1].wc))
1394 --cx;
1395 for (rv = 0; cword < cx; ++cword)
1396 rv += cword->count;
1397 sub =
1398 bot = orig;
1399 bot.l = rv;
1400 sub.s += rv;
1401 sub.l -= rv;
1402 sub.l -= topp.l;
1404 /* Leave room for "implicit asterisk" expansion, as below */
1405 if (sub.l == 0) {
1406 sub.s = UNCONST("*");
1407 sub.l = 1;
1408 } else {
1409 exp.s = salloc(sub.l + 1 +1);
1410 memcpy(exp.s, sub.s, sub.l);
1411 exp.s[sub.l] = '\0';
1412 sub.s = exp.s;
1415 /* TODO there is a TODO note upon fexpand() with multi-return;
1416 * TODO if that will change, the if() below can be simplified */
1417 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1418 jredo:
1419 hold_all_sigs();
1420 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1421 rele_all_sigs();
1423 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1424 goto jnope;
1425 /* If the expansion equals the original string, assume the user wants what
1426 * is usually known as tab completion, append `*' and restart */
1427 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1428 if (sub.s[sub.l - 1] == '*')
1429 goto jnope;
1430 sub.s[sub.l++] = '*';
1431 sub.s[sub.l] = '\0';
1432 goto jredo;
1435 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1436 * Take care to take *prompt* into account, since we don't know
1437 * anything about it's visual length (fputs(3) is used), simply
1438 * assume each character requires two columns */
1439 /* TODO the problem is that we loose control otherwise; in the best
1440 * TODO case the user can control via ^A and ^K etc., but be safe;
1441 * TODO we cannot simply adjust fexpand() because we don't know how
1442 * TODO that is implemented... The real solution would be to check
1443 * TODO wether we fit on a line, and start a pager if not.
1444 * TODO However, that should be part of a real tab-COMPLETION, then,
1445 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1446 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1447 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1448 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1449 exp.s = UNCONST("[ERR_TOO_LONG]");
1450 exp.l = sizeof("[ERR_TOO_LONG]") - 1;
1451 topp.l = 0;
1452 if (rv + bot.l + exp.l >= MAX_INPUT)
1453 bot.l = 0;
1454 if (rv + exp.l >= MAX_INPUT) {
1455 exp.s = UNCONST("[ERR]");
1456 exp.l = sizeof("[ERR]") - 1;
1460 orig.l = bot.l + exp.l + topp.l;
1461 orig.s = salloc(orig.l + 5 +1);
1462 if ((rv = bot.l) > 0)
1463 memcpy(orig.s, bot.s, rv);
1464 memcpy(orig.s + rv, exp.s, exp.l);
1465 rv += exp.l;
1466 if (topp.l > 0) {
1467 memcpy(orig.s + rv, topp.s, topp.l);
1468 rv += topp.l;
1470 orig.s[rv] = '\0';
1472 l->defc = orig;
1473 _ncl_khome(l, FAL0);
1474 _ncl_kkill(l, FAL0);
1475 jleave:
1476 NYD2_LEAVE;
1477 return rv;
1478 jnope:
1479 /* If we've provided a default content, but failed to expand, there is
1480 * nothing we can "revert to": drop that default again */
1481 if (set_savec)
1482 l->savec.s = NULL, l->savec.l = 0;
1483 rv = 0;
1484 goto jleave;
1486 # endif /* HAVE_TABEXPAND */
1488 static ssize_t
1489 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1490 SMALLOC_DEBUG_ARGS)
1492 /* We want to save code, yet we may have to incorporate a lines'
1493 * default content and / or default input to switch back to after some
1494 * history movement; let "len > 0" mean "have to display some data
1495 * buffer", and only otherwise read(2) it */
1496 mbstate_t ps[2];
1497 struct line l;
1498 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp, cursor_maybe, cursor_store;
1499 wchar_t wc;
1500 ssize_t rv;
1501 NYD_ENTER;
1503 memset(&l, 0, sizeof l);
1504 l.line.cbuf = *buf;
1505 if (len != 0) {
1506 l.defc.s = savestrbuf(*buf, len);
1507 l.defc.l = len;
1509 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1510 l.prompt = prompt = "?ERR?";
1511 /* TODO *l.nd=='\0' : instead adjust accmacvar.c to disallow empty vals */
1512 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1513 l.nd = "\033[C"; /* XXX no "magic" constant */
1514 l.x_buf = buf;
1515 l.x_bufsize = bufsize;
1517 if (prompt != NULL && *prompt != '\0')
1518 fputs(prompt, stdout);
1519 fflush(stdout);
1521 jrestart:
1522 memset(ps, 0, sizeof ps);
1523 cursor_maybe = cursor_store = 0;
1524 /* TODO: NCL: we should output the reset sequence when we jrestart:
1525 * TODO: NCL: if we are using a stateful encoding? !
1526 * TODO: NCL: in short: this is not yet well understood */
1527 for (;;) {
1528 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1530 /* Normal read(2)? Else buffer-takeover: speed this one up */
1531 if (len == 0)
1532 cbufp =
1533 cbuf = cbuf_base;
1534 else {
1535 assert(l.defc.l > 0 && l.defc.s != NULL);
1536 cbufp =
1537 cbuf = l.defc.s + (l.defc.l - len);
1538 cbufp += len;
1541 /* Read in the next complete multibyte character */
1542 for (;;) {
1543 if (len == 0) {
1544 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1545 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1546 continue;
1547 goto jleave;
1549 ++cbufp;
1552 /* Ach! the ISO C multibyte handling!
1553 * Encodings with locking shift states cannot really be helped, since
1554 * it is impossible to only query the shift state, as opposed to the
1555 * entire shift state + character pair (via ISO C functions) */
1556 rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), ps + 0);
1557 if (rv <= 0) {
1558 /* Any error during take-over can only result in a hard reset;
1559 * Otherwise, if it's a hard error, or if too many redundant shift
1560 * sequences overflow our buffer, also perform a hard reset */
1561 if (len != 0 || rv == -1 ||
1562 sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)) {
1563 l.savec.s = l.defc.s = NULL,
1564 l.savec.l = l.defc.l = len = 0;
1565 putchar('\a');
1566 wc = 'G';
1567 goto jreset;
1569 /* Otherwise, due to the way we deal with the buffer, we need to
1570 * restore the mbstate_t from before this conversion */
1571 ps[0] = ps[1];
1572 continue;
1575 if (len != 0 && (len -= (size_t)rv) == 0)
1576 l.defc.s = NULL, l.defc.l = 0;
1577 ps[1] = ps[0];
1578 break;
1581 /* Don't interpret control bytes during buffer take-over */
1582 if (cbuf != cbuf_base)
1583 goto jprint;
1584 switch (wc) {
1585 case 'A' ^ 0x40: /* cursor home */
1586 _ncl_khome(&l, TRU1);
1587 break;
1588 case 'B' ^ 0x40: /* backward character */
1589 j_b:
1590 _ncl_kleft(&l);
1591 break;
1592 /* 'C': interrupt (CTRL-C) */
1593 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1594 if ((rv = _ncl_keof(&l)) < 0)
1595 goto jleave;
1596 break;
1597 case 'E' ^ 0x40: /* end of line */
1598 _ncl_kend(&l);
1599 break;
1600 case 'F' ^ 0x40: /* forward character */
1601 j_f:
1602 _ncl_kright(&l);
1603 break;
1604 /* 'G' below */
1605 case 'H' ^ 0x40: /* backspace */
1606 case '\177':
1607 _ncl_kbs(&l);
1608 break;
1609 case 'I' ^ 0x40: /* horizontal tab */
1610 # ifdef HAVE_TABEXPAND
1611 if ((len = _ncl_kht(&l)) > 0)
1612 goto jrestart;
1613 # endif
1614 goto jbell;
1615 case 'J' ^ 0x40: /* NL (\n) */
1616 goto jdone;
1617 case 'G' ^ 0x40: /* full reset */
1618 jreset:
1619 /* FALLTHRU */
1620 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1621 _ncl_khome(&l, FAL0);
1622 /* FALLTHRU */
1623 case 'K' ^ 0x40: /* kill from cursor to end of line */
1624 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1625 /* (Handle full reset?) */
1626 if (wc == ('G' ^ 0x40)) {
1627 # ifdef HAVE_HISTORY
1628 l.hist = NULL;
1629 # endif
1630 if ((len = l.savec.l) != 0) {
1631 l.defc = l.savec;
1632 l.savec.s = NULL, l.savec.l = 0;
1633 } else
1634 len = l.defc.l;
1636 fflush(stdout);
1637 goto jrestart;
1638 case 'L' ^ 0x40: /* repaint line */
1639 j_l:
1640 _ncl_krefresh(&l);
1641 break;
1642 /* 'M': CR (\r) */
1643 case 'N' ^ 0x40: /* history next */
1644 j_n:
1645 # ifdef HAVE_HISTORY
1646 if (l.hist == NULL)
1647 goto jbell;
1648 if ((len = _ncl_khist(&l, FAL0)) > 0)
1649 goto jrestart;
1650 wc = 'G' ^ 0x40;
1651 goto jreset;
1652 # else
1653 goto jbell;
1654 # endif
1655 /* 'O' */
1656 case 'O' ^ 0x40: /* `dp' */
1657 putchar('\n');
1658 cbuf_base[0] = 'd';
1659 cbuf_base[1] = 'p';
1660 cbuf_base[2] = '\0';
1661 pstate &= ~PS_HOOK_MASK;
1662 execute(cbuf_base, 2);
1663 goto j_l;
1664 case 'P' ^ 0x40: /* history previous */
1665 j_p:
1666 # ifdef HAVE_HISTORY
1667 if ((len = _ncl_khist(&l, TRU1)) > 0)
1668 goto jrestart;
1669 wc = 'G' ^ 0x40;
1670 goto jreset;
1671 # else
1672 goto jbell;
1673 # endif
1674 /* 'Q': no code */
1675 case 'R' ^ 0x40: /* reverse history search */
1676 # ifdef HAVE_HISTORY
1677 if ((len = _ncl_krhist(&l)) > 0)
1678 goto jrestart;
1679 wc = 'G' ^ 0x40;
1680 goto jreset;
1681 # else
1682 goto jbell;
1683 # endif
1684 /* 'S': no code */
1685 /* 'U' above */
1686 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1687 case 'W' ^ 0x40: /* backward delete "word" */
1688 _ncl_kbwddelw(&l);
1689 break;
1690 case 'X' ^ 0x40: /* move cursor forward "word" */
1691 _ncl_kgow(&l, +1);
1692 break;
1693 case 'Y' ^ 0x40: /* move cursor backward "word" */
1694 _ncl_kgow(&l, -1);
1695 break;
1696 /* 'Z': suspend (CTRL-Z) */
1697 case 0x1B:
1698 if (cursor_maybe++ != 0)
1699 goto jreset;
1700 continue;
1701 default:
1702 /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */
1703 if (cursor_maybe > 0) {
1704 if (++cursor_maybe == 2) {
1705 if (wc == L'[')
1706 continue;
1707 cursor_maybe = 0;
1708 } else if (cursor_maybe == 3) {
1709 cursor_maybe = 0;
1710 switch (wc) {
1711 default: break;
1712 case L'A': goto j_p;
1713 case L'B': goto j_n;
1714 case L'C': goto j_f;
1715 case L'D': goto j_b;
1716 case L'H':
1717 cursor_store = '0';
1718 goto J_xterm_noapp;
1719 case L'F':
1720 cursor_store = '$';
1721 goto J_xterm_noapp;
1722 case L'1':
1723 case L'4':
1724 case L'5':
1725 case L'6':
1726 cursor_store = ((wc == L'1') ? '0' :
1727 (wc == L'4' ? '$' : (wc == L'5' ? '-' : '+')));
1728 cursor_maybe = 3;
1729 continue;
1731 _ncl_kother(&l, L'[');
1732 } else {
1733 cursor_maybe = 0;
1734 if (wc == L'~')
1735 J_xterm_noapp: {
1736 char x[2];
1737 x[0] = cursor_store;
1738 x[1] = '\0';
1739 putchar('\n');
1740 c_scroll(x);
1741 cursor_store = 0;
1742 goto j_l;
1743 } else if (cursor_store == '-' && (wc == L'A' || wc == L'B')) {
1744 char x[2];
1745 x[0] = (wc != L'A') ? '+' : cursor_store;
1746 x[1] = '\0';
1747 putchar('\n');
1748 c_dotmove(x);
1749 cursor_store = 0;
1750 goto j_l;
1752 _ncl_kother(&l, L'[');
1753 _ncl_kother(&l, (wchar_t)cursor_store);
1754 cursor_store = 0;
1757 jprint:
1758 if (iswprint(wc)) {
1759 _ncl_kother(&l, wc);
1760 /* Don't clear the history during takeover..
1761 * ..and also avoid fflush()ing unless we've worked entire buffer */
1762 if (len > 0)
1763 continue;
1764 # ifdef HAVE_HISTORY
1765 if (cbuf == cbuf_base)
1766 l.hist = NULL;
1767 # endif
1768 } else {
1769 jbell:
1770 putchar('\a');
1772 break;
1774 fflush(stdout);
1777 /* We have a completed input line, convert the struct cell data to its
1778 * plain character equivalent */
1779 jdone:
1780 putchar('\n');
1781 fflush(stdout);
1782 len = _ncl_cell2dat(&l);
1783 rv = (ssize_t)len;
1784 jleave:
1785 NYD_LEAVE;
1786 return rv;
1789 FL void
1790 n_tty_init(void)
1792 # ifdef HAVE_HISTORY
1793 long hs;
1794 char const *v;
1795 char *lbuf;
1796 FILE *f;
1797 size_t lsize, cnt, llen;
1798 # endif
1799 NYD_ENTER;
1801 # ifdef HAVE_HISTORY
1802 _CL_HISTSIZE(hs);
1803 _ncl_hist_size = 0;
1804 _ncl_hist_size_max = hs;
1805 if (hs == 0)
1806 goto jleave;
1808 _CL_HISTFILE(v);
1809 if (v == NULL)
1810 goto jleave;
1812 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1813 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1814 if (f == NULL)
1815 goto jdone;
1816 (void)file_lock(fileno(f), FLT_READ, 0,0, 500);
1818 lbuf = NULL;
1819 lsize = 0;
1820 cnt = fsize(f);
1821 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1822 if (llen > 0 && lbuf[llen - 1] == '\n')
1823 lbuf[--llen] = '\0';
1824 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1825 continue;
1826 else {
1827 bool_t isgabby = (lbuf[0] == '*');
1828 _ncl_hist_load = TRU1;
1829 n_tty_addhist(lbuf + isgabby, isgabby);
1830 _ncl_hist_load = FAL0;
1833 if (lbuf != NULL)
1834 free(lbuf);
1836 fclose(f);
1837 jdone:
1838 rele_all_sigs(); /* XXX remove jumps */
1839 jleave:
1840 # endif /* HAVE_HISTORY */
1841 NYD_LEAVE;
1844 FL void
1845 n_tty_destroy(void)
1847 # ifdef HAVE_HISTORY
1848 long hs;
1849 char const *v;
1850 struct hist *hp;
1851 bool_t dogabby;
1852 FILE *f;
1853 # endif
1854 NYD_ENTER;
1856 # ifdef HAVE_HISTORY
1857 _CL_HISTSIZE(hs);
1858 if (hs == 0)
1859 goto jleave;
1860 _CL_HISTFILE(v);
1861 if (v == NULL)
1862 goto jleave;
1864 dogabby = ok_blook(history_gabby_persist);
1866 if ((hp = _ncl_hist) != NULL)
1867 for (; hp->older != NULL; hp = hp->older)
1868 if ((dogabby || !hp->isgabby) && --hs == 0)
1869 break;
1871 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1872 f = fopen(v, "w"); /* TODO temporary + rename?! */
1873 if (f == NULL)
1874 goto jdone;
1875 (void)file_lock(fileno(f), FLT_WRITE, 0,0, 500);
1876 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1877 goto jclose;
1879 for (; hp != NULL; hp = hp->younger) {
1880 if (!hp->isgabby || dogabby) {
1881 if (hp->isgabby)
1882 putc('*', f);
1883 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1884 putc('\n', f);
1887 jclose:
1888 fclose(f);
1889 jdone:
1890 rele_all_sigs(); /* XXX remove jumps */
1891 jleave:
1892 # endif /* HAVE_HISTORY */
1893 NYD_LEAVE;
1896 FL void
1897 n_tty_signal(int sig)
1899 sigset_t nset, oset;
1900 NYD_X; /* Signal handler */
1902 switch (sig) {
1903 case SIGWINCH:
1904 /* We don't deal with SIGWINCH, yet get called from main.c */
1905 break;
1906 default:
1907 _ncl_term_mode(FAL0);
1908 _ncl_sigs_down();
1909 sigemptyset(&nset);
1910 sigaddset(&nset, sig);
1911 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1912 n_raise(sig);
1913 /* When we come here we'll continue editing, so reestablish */
1914 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1915 _ncl_sigs_up();
1916 _ncl_term_mode(TRU1);
1917 break;
1921 FL int
1922 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1923 SMALLOC_DEBUG_ARGS)
1925 ssize_t nn;
1926 NYD_ENTER;
1928 /* Of course we have races here, but they cannot be avoided on POSIX
1929 * (except by even *more* actions) */
1930 _ncl_sigs_up();
1931 _ncl_term_mode(TRU1);
1932 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1933 _ncl_term_mode(FAL0);
1934 _ncl_sigs_down();
1935 NYD_LEAVE;
1936 return (int)nn;
1939 FL void
1940 n_tty_addhist(char const *s, bool_t isgabby)
1942 # ifdef HAVE_HISTORY
1943 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1944 ui32_t l;
1945 struct hist *h, *o, *y;
1946 # endif
1947 NYD_ENTER;
1948 UNUSED(s);
1949 UNUSED(isgabby);
1951 # ifdef HAVE_HISTORY
1952 if (isgabby && !ok_blook(history_gabby))
1953 goto j_leave;
1954 if (_ncl_hist_size_max == 0)
1955 goto j_leave;
1956 _CL_CHECK_ADDHIST(s, goto j_leave);
1958 l = (ui32_t)strlen(s);
1960 /* Eliminating duplicates is expensive, but simply inacceptable so
1961 * during the load of a potentially large history file! */
1962 if (!_ncl_hist_load)
1963 for (h = _ncl_hist; h != NULL; h = h->older)
1964 if (h->len == l && !strcmp(h->dat, s)) {
1965 hold_all_sigs(); /* TODO */
1966 if (h->isgabby)
1967 h->isgabby = !!isgabby;
1968 o = h->older;
1969 y = h->younger;
1970 if (o != NULL)
1971 o->younger = y;
1972 else
1973 _ncl_hist_tail = y;
1974 if (y != NULL)
1975 y->older = o;
1976 else
1977 _ncl_hist = o;
1978 goto jleave;
1980 hold_all_sigs();
1982 ++_ncl_hist_size;
1983 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1984 --_ncl_hist_size;
1985 if ((h = _ncl_hist_tail) != NULL) {
1986 if ((_ncl_hist_tail = h->younger) == NULL)
1987 _ncl_hist = NULL;
1988 else
1989 _ncl_hist_tail->older = NULL;
1990 free(h);
1994 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1995 h->isgabby = !!isgabby;
1996 h->len = l;
1997 memcpy(h->dat, s, l +1);
1998 jleave:
1999 if ((h->older = _ncl_hist) != NULL)
2000 _ncl_hist->younger = h;
2001 else
2002 _ncl_hist_tail = h;
2003 h->younger = NULL;
2004 _ncl_hist = h;
2006 rele_all_sigs();
2007 j_leave:
2008 # endif
2009 NYD_LEAVE;
2012 # ifdef HAVE_HISTORY
2013 FL int
2014 c_history(void *v)
2016 C_HISTORY_SHARED;
2018 jlist: {
2019 FILE *fp;
2020 size_t i, b;
2021 struct hist *h;
2023 if (_ncl_hist == NULL)
2024 goto jleave;
2026 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
2027 n_perr(_("tmpfile"), 0);
2028 v = NULL;
2029 goto jleave;
2032 i = _ncl_hist_size;
2033 b = 0;
2034 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
2035 fprintf(fp,
2036 "%c%4" PRIuZ ". %-50.50s (%4" PRIuZ "+%2" PRIu32 " bytes)\n",
2037 (h->isgabby ? '*' : ' '), i, h->dat, b, h->len);
2039 page_or_print(fp, i);
2040 Fclose(fp);
2042 goto jleave;
2044 jclear: {
2045 struct hist *h;
2047 while ((h = _ncl_hist) != NULL) {
2048 _ncl_hist = h->older;
2049 free(h);
2051 _ncl_hist_tail = NULL;
2052 _ncl_hist_size = 0;
2054 goto jleave;
2056 jentry: {
2057 struct hist *h;
2059 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2060 entry = (long)_ncl_hist_size - entry;
2061 for (h = _ncl_hist;; h = h->older)
2062 if (h == NULL)
2063 break;
2064 else if (entry-- != 0)
2065 continue;
2066 else {
2067 v = temporary_arg_v_store = h->dat;
2068 goto jleave;
2071 v = NULL;
2073 goto jleave;
2075 # endif /* HAVE_HISTORY */
2076 #endif /* HAVE_NCL */
2079 * The really-nothing-at-all implementation
2082 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2083 FL void
2084 n_tty_init(void)
2086 NYD_ENTER;
2087 NYD_LEAVE;
2090 FL void
2091 n_tty_destroy(void)
2093 NYD_ENTER;
2094 NYD_LEAVE;
2097 FL void
2098 n_tty_signal(int sig)
2100 NYD_X; /* Signal handler */
2101 UNUSED(sig);
2104 FL int
2105 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2106 SMALLOC_DEBUG_ARGS)
2108 int rv;
2109 NYD_ENTER;
2111 if (prompt != NULL) {
2112 if (*prompt != '\0')
2113 fputs(prompt, stdout);
2114 fflush(stdout);
2116 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2117 NYD_LEAVE;
2118 return rv;
2121 FL void
2122 n_tty_addhist(char const *s, bool_t isgabby)
2124 NYD_ENTER;
2125 UNUSED(s);
2126 UNUSED(isgabby);
2127 NYD_LEAVE;
2129 #endif /* nothing at all */
2131 /* s-it-mode */