FIX "," message specification (since [1c4b8c9], v14.8.4)..
[s-mailx.git] / tty.c
blobef5c7eade367b210bbc5c73b23dd88cb741bfb0a
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 S = ok_vlook(NAIL_HISTFILE);\
69 if ((S) != NULL)\
70 S = fexpand(S, FEXP_LOCAL);\
71 } while (0)
73 # define _CL_HISTSIZE(V) \
74 do {\
75 char const *__sv = ok_vlook(NAIL_HISTSIZE);\
76 long __rv;\
77 if (__sv == NULL || *__sv == '\0' || (__rv = strtol(__sv, NULL, 10)) == 0)\
78 (V) = HIST_SIZE;\
79 else if (__rv < 0)\
80 (V) = 0;\
81 else\
82 (V) = __rv;\
83 } while (0)
85 # define _CL_CHECK_ADDHIST(S,NOACT) \
86 do {\
87 switch (*(S)) {\
88 case '\0':\
89 case ' ':\
90 NOACT;\
91 default:\
92 break;\
94 } while (0)
96 # define C_HISTORY_SHARED \
97 char **argv = v;\
98 long entry;\
99 NYD_ENTER;\
101 if (*argv == NULL)\
102 goto jlist;\
103 if (argv[1] != NULL)\
104 goto jerr;\
105 if (!asccasecmp(*argv, "show"))\
106 goto jlist;\
107 if (!asccasecmp(*argv, "clear"))\
108 goto jclear;\
109 if ((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
110 goto jentry;\
111 jerr:\
112 n_err(_("Synopsis: history: %s\n" \
113 "<show> (default), <clear> or select <NO> from editor history"));\
114 v = NULL;\
115 jleave:\
116 NYD_LEAVE;\
117 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
118 #endif /* HAVE_HISTORY */
120 /* fexpand() flags for expand-on-tab */
121 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
124 * Because we have multiple identical implementations, change file layout a bit
125 * and place the implementations one after the other below the other externals
128 static sigjmp_buf __tty_actjmp; /* TODO someday, we won't need it no more */
129 static void
130 __tty_acthdl(int s) /* TODO someday, we won't need it no more */
132 NYD_X; /* Signal handler */
133 termios_state_reset();
134 siglongjmp(__tty_actjmp, s);
137 FL bool_t
138 getapproval(char const * volatile prompt, bool_t noninteract_default)
140 sighandler_type volatile oint, ohup;
141 bool_t volatile rv;
142 int volatile sig;
143 NYD_ENTER;
145 if (!(options & OPT_INTERACTIVE)) {
146 sig = 0;
147 rv = noninteract_default;
148 goto jleave;
150 rv = FAL0;
152 if (prompt == NULL)
153 prompt = noninteract_default ? _(" ([yes]/no)? ") : _(" ([no]/yes)? ");
155 oint = safe_signal(SIGINT, SIG_IGN);
156 ohup = safe_signal(SIGHUP, SIG_IGN);
157 if ((sig = sigsetjmp(__tty_actjmp, 1)) != 0)
158 goto jrestore;
159 safe_signal(SIGINT, &__tty_acthdl);
160 safe_signal(SIGHUP, &__tty_acthdl);
162 if (readline_input(prompt, FAL0, &termios_state.ts_linebuf,
163 &termios_state.ts_linesize, NULL) >= 0)
164 rv = (boolify(termios_state.ts_linebuf, UIZ_MAX,
165 noninteract_default) > 0);
166 jrestore:
167 termios_state_reset();
169 safe_signal(SIGHUP, ohup);
170 safe_signal(SIGINT, oint);
171 jleave:
172 NYD_LEAVE;
173 if (sig != 0)
174 n_raise(sig);
175 return rv;
178 #ifdef HAVE_SOCKETS
179 FL char *
180 getuser(char const * volatile query) /* TODO v15-compat obsolete */
182 sighandler_type volatile oint, ohup;
183 char * volatile user = NULL;
184 int volatile sig;
185 NYD_ENTER;
187 if (query == NULL)
188 query = _("User: ");
190 oint = safe_signal(SIGINT, SIG_IGN);
191 ohup = safe_signal(SIGHUP, SIG_IGN);
192 if ((sig = sigsetjmp(__tty_actjmp, 1)) != 0)
193 goto jrestore;
194 safe_signal(SIGINT, &__tty_acthdl);
195 safe_signal(SIGHUP, &__tty_acthdl);
197 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
198 &termios_state.ts_linesize, NULL) >= 0)
199 user = termios_state.ts_linebuf;
200 jrestore:
201 termios_state_reset();
203 safe_signal(SIGHUP, ohup);
204 safe_signal(SIGINT, oint);
205 NYD_LEAVE;
206 if (sig != 0)
207 n_raise(sig);
208 return user;
211 FL char *
212 getpassword(char const *query)
214 sighandler_type volatile oint, ohup;
215 struct termios tios;
216 char * volatile pass = NULL;
217 int volatile sig;
218 NYD_ENTER;
220 if (query == NULL)
221 query = _("Password: ");
222 fputs(query, stdout);
223 fflush(stdout);
225 /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
226 * FIXME foreground pgrp, and can fail with EINTR!! also affects
227 * FIXME termios_state_reset() */
228 if (options & OPT_TTYIN) {
229 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
230 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
231 termios_state.ts_needs_reset = TRU1;
232 tios.c_iflag &= ~(ISTRIP);
233 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
236 oint = safe_signal(SIGINT, SIG_IGN);
237 ohup = safe_signal(SIGHUP, SIG_IGN);
238 if ((sig = sigsetjmp(__tty_actjmp, 1)) != 0)
239 goto jrestore;
240 safe_signal(SIGINT, &__tty_acthdl);
241 safe_signal(SIGHUP, &__tty_acthdl);
243 if (options & OPT_TTYIN)
244 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
246 if (readline_restart(stdin, &termios_state.ts_linebuf,
247 &termios_state.ts_linesize, 0) >= 0)
248 pass = termios_state.ts_linebuf;
249 jrestore:
250 termios_state_reset();
251 if (options & OPT_TTYIN)
252 putc('\n', stdout);
254 safe_signal(SIGHUP, ohup);
255 safe_signal(SIGINT, oint);
256 NYD_LEAVE;
257 if (sig != 0)
258 n_raise(sig);
259 return pass;
261 #endif /* HAVE_SOCKETS */
264 * readline(3)
267 #ifdef HAVE_READLINE
268 static sighandler_type _rl_shup;
269 static char * _rl_buf; /* pre_input() hook: initial line */
270 static int _rl_buflen; /* content, and its length */
272 static int _rl_pre_input(void);
274 static int
275 _rl_pre_input(void)
277 NYD_ENTER;
278 /* Handle leftover data from \ escaped former line */
279 rl_extend_line_buffer(_rl_buflen + 10);
280 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
281 rl_point = rl_end = _rl_buflen;
282 rl_pre_input_hook = (rl_hook_func_t*)NULL;
283 rl_redisplay();
284 NYD_LEAVE;
285 return 0;
288 FL void
289 tty_init(void)
291 # ifdef HAVE_HISTORY
292 long hs;
293 char *v;
294 # endif
295 NYD_ENTER;
297 rl_readline_name = UNCONST(uagent);
298 # ifdef HAVE_HISTORY
299 _CL_HISTSIZE(hs);
300 using_history();
301 stifle_history((int)hs);
302 # endif
303 rl_read_init_file(NULL);
305 /* Because rl_read_init_file() may have introduced yet a different
306 * history size limit, simply load and incorporate the history, leave
307 * it up to readline(3) to do the rest */
308 # ifdef HAVE_HISTORY
309 _CL_HISTFILE(v);
310 if (v != NULL)
311 read_history(v);
312 # endif
313 NYD_LEAVE;
316 FL void
317 tty_destroy(void)
319 # ifdef HAVE_HISTORY
320 char *v;
321 # endif
322 NYD_ENTER;
324 # ifdef HAVE_HISTORY
325 _CL_HISTFILE(v);
326 if (v != NULL)
327 write_history(v);
328 # endif
329 NYD_LEAVE;
332 FL void
333 tty_signal(int sig)
335 sigset_t nset, oset;
336 NYD_X; /* Signal handler */
338 switch (sig) {
339 # ifdef SIGWINCH
340 case SIGWINCH:
341 break;
342 # endif
343 case SIGHUP:
344 /* readline(3) doesn't catch it :( */
345 rl_free_line_state();
346 rl_cleanup_after_signal();
347 safe_signal(SIGHUP, _rl_shup);
348 sigemptyset(&nset);
349 sigaddset(&nset, sig);
350 sigprocmask(SIG_UNBLOCK, &nset, &oset);
351 n_raise(sig);
352 /* XXX When we come here we'll continue editing, so reestablish
353 * XXX cannot happen */
354 sigprocmask(SIG_BLOCK, &oset, NULL);
355 _rl_shup = safe_signal(SIGHUP, &tty_signal);
356 rl_reset_after_signal();
357 break;
358 default:
359 break;
363 FL int
364 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
365 SMALLOC_DEBUG_ARGS)
367 int nn;
368 char *line;
369 NYD_ENTER;
371 if (n > 0) {
372 _rl_buf = *linebuf;
373 _rl_buflen = (int)n;
374 rl_pre_input_hook = &_rl_pre_input;
377 _rl_shup = safe_signal(SIGHUP, &tty_signal);
378 line = readline(prompt != NULL ? prompt : "");
379 safe_signal(SIGHUP, _rl_shup);
381 if (line == NULL) {
382 nn = -1;
383 goto jleave;
385 n = strlen(line);
387 if (n >= *linesize) {
388 *linesize = LINESIZE + n +1;
389 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
391 memcpy(*linebuf, line, n);
392 (free)(line);
393 (*linebuf)[n] = '\0';
394 nn = (int)n;
395 jleave:
396 NYD_LEAVE;
397 return nn;
400 FL void
401 tty_addhist(char const *s, bool_t isgabby)
403 NYD_ENTER;
404 UNUSED(s);
405 UNUSED(isgabby);
406 # ifdef HAVE_HISTORY
407 if (isgabby && !ok_blook(history_gabby))
408 goto jleave;
409 _CL_CHECK_ADDHIST(s, goto jleave);
410 hold_all_sigs(); /* XXX too heavy */
411 add_history(s); /* XXX yet we jump away! */
412 rele_all_sigs(); /* XXX remove jumps */
413 jleave:
414 # endif
415 NYD_LEAVE;
418 # ifdef HAVE_HISTORY
419 FL int
420 c_history(void *v)
422 C_HISTORY_SHARED;
424 jlist: {
425 FILE *fp;
426 HISTORY_STATE *hs;
427 HIST_ENTRY **hl;
428 ul_i i, b;
430 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
431 NULL) {
432 n_perr(_("tmpfile"), 0);
433 v = NULL;
434 goto jleave;
437 hs = history_get_history_state();
439 for (i = (ul_i)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
440 char *cp = (*--hl)->line;
441 size_t sl = strlen(cp);
442 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n", i, cp, b, sl);
443 b += sl;
446 page_or_print(fp, (size_t)hs->length);
447 Fclose(fp);
449 goto jleave;
451 jclear:
452 clear_history();
453 goto jleave;
455 jentry: {
456 HISTORY_STATE *hs = history_get_history_state();
458 if (UICMP(z, entry, <=, hs->length))
459 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
460 else
461 v = NULL;
463 goto jleave;
465 # endif /* HAVE_HISTORY */
466 #endif /* HAVE_READLINE */
469 * BSD editline(3)
472 #ifdef HAVE_EDITLINE
473 static EditLine * _el_el; /* editline(3) handle */
474 static char const * _el_prompt; /* Current prompt */
475 # ifdef HAVE_HISTORY
476 static History * _el_hcom; /* History handle for commline */
477 # endif
479 static char const * _el_getprompt(void);
481 static char const *
482 _el_getprompt(void)
484 return _el_prompt;
487 FL void
488 tty_init(void)
490 # ifdef HAVE_HISTORY
491 HistEvent he;
492 long hs;
493 char *v;
494 # endif
495 NYD_ENTER;
497 # ifdef HAVE_HISTORY
498 _CL_HISTSIZE(hs);
499 _el_hcom = history_init();
500 history(_el_hcom, &he, H_SETSIZE, (int)hs);
501 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
502 # endif
504 _el_el = el_init(uagent, stdin, stdout, stderr);
505 el_set(_el_el, EL_SIGNAL, 1);
506 el_set(_el_el, EL_TERMINAL, NULL);
507 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
508 # ifdef HAVE_HISTORY
509 el_set(_el_el, EL_HIST, &history, _el_hcom);
510 # endif
511 el_set(_el_el, EL_EDITOR, "emacs");
512 # ifdef EL_PROMPT_ESC
513 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
514 # else
515 el_set(_el_el, EL_PROMPT, &_el_getprompt);
516 # endif
517 # if 0
518 el_set(_el_el, EL_ADDFN, "tab_complete",
519 "editline(3) internal completion function", &_el_file_cpl);
520 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
521 # endif
522 # ifdef HAVE_HISTORY
523 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
524 # endif
525 el_source(_el_el, NULL); /* Source ~/.editrc */
527 /* Because el_source() may have introduced yet a different history size
528 * limit, simply load and incorporate the history, leave it up to
529 * editline(3) to do the rest */
530 # ifdef HAVE_HISTORY
531 _CL_HISTFILE(v);
532 if (v != NULL)
533 history(_el_hcom, &he, H_LOAD, v);
534 # endif
535 NYD_LEAVE;
538 FL void
539 tty_destroy(void)
541 # ifdef HAVE_HISTORY
542 HistEvent he;
543 char *v;
544 # endif
545 NYD_ENTER;
547 el_end(_el_el);
549 # ifdef HAVE_HISTORY
550 _CL_HISTFILE(v);
551 if (v != NULL)
552 history(_el_hcom, &he, H_SAVE, v);
553 history_end(_el_hcom);
554 # endif
555 NYD_LEAVE;
558 FL void
559 tty_signal(int sig)
561 NYD_X; /* Signal handler */
562 switch (sig) {
563 # ifdef SIGWINCH
564 case SIGWINCH:
565 el_resize(_el_el);
566 break;
567 # endif
568 default:
569 break;
573 FL int
574 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
575 SMALLOC_DEBUG_ARGS)
577 int nn;
578 char const *line;
579 NYD_ENTER;
581 _el_prompt = (prompt != NULL) ? prompt : "";
582 if (n > 0)
583 el_push(_el_el, *linebuf);
584 line = el_gets(_el_el, &nn);
586 if (line == NULL) {
587 nn = -1;
588 goto jleave;
590 assert(nn >= 0);
591 n = (size_t)nn;
592 if (n > 0 && line[n - 1] == '\n')
593 nn = (int)--n;
595 if (n >= *linesize) {
596 *linesize = LINESIZE + n + 1;
597 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
599 memcpy(*linebuf, line, n);
600 (*linebuf)[n] = '\0';
601 jleave:
602 NYD_LEAVE;
603 return nn;
606 FL void
607 tty_addhist(char const *s, bool_t isgabby)
609 # ifdef HAVE_HISTORY
610 /* Enlarge meaning of unique .. to something that rocks;
611 * xxx unfortunately this is expensive to do with editline(3)
612 * xxx maybe it would be better to hook the ptfs instead? */
613 HistEvent he;
614 int i;
615 # endif
616 NYD_ENTER;
617 UNUSED(s);
618 UNUSED(isgabby);
620 # ifdef HAVE_HISTORY
621 if (isgabby && !ok_blook(history_gabby))
622 goto jleave;
623 _CL_CHECK_ADDHIST(s, goto jleave);
625 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
626 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
627 i = history(_el_hcom, &he, H_NEXT))
628 if (!strcmp(he.str, s)) {
629 history(_el_hcom, &he, H_DEL, he.num);
630 break;
632 history(_el_hcom, &he, H_ENTER, s);
633 rele_all_sigs(); /* XXX remove jumps */
634 jleave:
635 # endif
636 NYD_LEAVE;
639 # ifdef HAVE_HISTORY
640 FL int
641 c_history(void *v)
643 C_HISTORY_SHARED;
645 jlist: {
646 HistEvent he;
647 FILE *fp;
648 size_t i, b;
649 int x;
651 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
652 NULL) {
653 n_perr(_("tmpfile"), 0);
654 v = NULL;
655 goto jleave;
658 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
659 b = 0;
660 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
661 x = history(_el_hcom, &he, H_NEXT)) {
662 size_t sl = strlen(he.str);
663 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n",
664 (ul_i)i, he.str, (ul_i)b, (ul_i)sl);
665 --i;
666 b += sl;
669 page_or_print(fp, i);
670 Fclose(fp);
672 goto jleave;
674 jclear: {
675 HistEvent he;
676 history(_el_hcom, &he, H_CLEAR);
678 goto jleave;
680 jentry: {
681 HistEvent he;
682 size_t i;
683 int x;
685 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
686 if (UICMP(z, entry, <=, i)) {
687 entry = (long)i - entry;
688 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
689 x = history(_el_hcom, &he, H_NEXT))
690 if (entry-- == 0) {
691 v = temporary_arg_v_store = UNCONST(he.str);
692 goto jleave;
695 v = NULL;
697 goto jleave;
699 # endif /* HAVE_HISTORY */
700 #endif /* HAVE_EDITLINE */
703 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
705 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
706 * We do not handle character widths because the terminal must deal with that
707 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
708 * characters by definition on the other. We're addicted.
710 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
711 * we're forced to use the very same buffer--the one that is passed through to
712 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
713 * convert that on-the-fly back to the plain char* result once we're done.
714 * To simplify our live, use savestr() buffers for all other needed memory
717 #ifdef HAVE_NCL
718 # ifndef MAX_INPUT
719 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
720 # endif
722 /* Since we simply fputs(3) the prompt, assume each character requires two
723 * visual cells -- and we need to restrict the maximum prompt size because
724 * of MAX_INPUT and our desire to have room for some error message left */
725 # define _PROMPT_VLEN(P) (strlen(P) * 2)
726 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
728 struct xtios {
729 struct termios told;
730 struct termios tnew;
733 struct cell {
734 wchar_t wc;
735 ui32_t count;
736 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
739 struct line {
740 size_t cursor; /* Current cursor position */
741 size_t topins; /* Outermost cursor col set */
742 union {
743 char *cbuf; /* *x_buf */
744 struct cell *cells;
745 } line;
746 struct str defc; /* Current default content */
747 struct str savec; /* Saved default content */
748 # ifdef HAVE_HISTORY
749 struct hist *hist; /* History cursor */
750 # endif
751 char const *prompt;
752 char const *nd; /* Cursor right */
753 char **x_buf; /* Caller pointers */
754 size_t *x_bufsize;
757 # ifdef HAVE_HISTORY
758 struct hist {
759 struct hist *older;
760 struct hist *younger;
761 ui32_t isgabby : 1;
762 ui32_t len : 31;
763 char dat[VFIELD_SIZE(sizeof(ui32_t))];
765 # endif
767 static sighandler_type _ncl_oint;
768 static sighandler_type _ncl_oquit;
769 static sighandler_type _ncl_oterm;
770 static sighandler_type _ncl_ohup;
771 static sighandler_type _ncl_otstp;
772 static sighandler_type _ncl_ottin;
773 static sighandler_type _ncl_ottou;
774 static struct xtios _ncl_tios;
775 # ifdef HAVE_HISTORY
776 static struct hist *_ncl_hist;
777 static struct hist *_ncl_hist_tail;
778 static size_t _ncl_hist_size;
779 static size_t _ncl_hist_size_max;
780 static bool_t _ncl_hist_load;
781 # endif
783 static void _ncl_sigs_up(void);
784 static void _ncl_sigs_down(void);
786 static void _ncl_term_mode(bool_t raw);
788 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
789 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
790 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
791 static ssize_t _ncl_cell2dat(struct line *l);
792 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
793 static void _ncl_cell2save(struct line *l);
794 # endif
796 static void _ncl_khome(struct line *l, bool_t dobell);
797 static void _ncl_kend(struct line *l);
798 static void _ncl_kbs(struct line *l);
799 static void _ncl_kkill(struct line *l, bool_t dobell);
800 static ssize_t _ncl_keof(struct line *l);
801 static void _ncl_kleft(struct line *l);
802 static void _ncl_kright(struct line *l);
803 static void _ncl_krefresh(struct line *l);
804 static void _ncl_kbwddelw(struct line *l);
805 static void _ncl_kgow(struct line *l, ssize_t dir);
806 static void _ncl_kother(struct line *l, wchar_t wc);
807 # ifdef HAVE_HISTORY
808 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
809 static size_t _ncl_khist(struct line *l, bool_t backwd);
810 static size_t _ncl_krhist(struct line *l);
811 # endif
812 # ifdef HAVE_TABEXPAND
813 static size_t _ncl_kht(struct line *l);
814 # endif
815 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
816 size_t len SMALLOC_DEBUG_ARGS);
818 static void
819 _ncl_sigs_up(void)
821 sigset_t nset, oset;
822 NYD2_ENTER;
824 sigfillset(&nset);
826 sigprocmask(SIG_BLOCK, &nset, &oset);
827 _ncl_oint = safe_signal(SIGINT, &tty_signal);
828 _ncl_oquit = safe_signal(SIGQUIT, &tty_signal);
829 _ncl_oterm = safe_signal(SIGTERM, &tty_signal);
830 _ncl_ohup = safe_signal(SIGHUP, &tty_signal);
831 _ncl_otstp = safe_signal(SIGTSTP, &tty_signal);
832 _ncl_ottin = safe_signal(SIGTTIN, &tty_signal);
833 _ncl_ottou = safe_signal(SIGTTOU, &tty_signal);
834 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
835 NYD2_LEAVE;
838 static void
839 _ncl_sigs_down(void)
841 sigset_t nset, oset;
842 NYD2_ENTER;
844 sigfillset(&nset);
846 sigprocmask(SIG_BLOCK, &nset, &oset);
847 safe_signal(SIGINT, _ncl_oint);
848 safe_signal(SIGQUIT, _ncl_oquit);
849 safe_signal(SIGTERM, _ncl_oterm);
850 safe_signal(SIGHUP, _ncl_ohup);
851 safe_signal(SIGTSTP, _ncl_otstp);
852 safe_signal(SIGTTIN, _ncl_ottin);
853 safe_signal(SIGTTOU, _ncl_ottou);
854 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
855 NYD2_LEAVE;
858 static void
859 _ncl_term_mode(bool_t raw)
861 struct termios *tiosp;
862 NYD2_ENTER;
864 tiosp = &_ncl_tios.told;
865 if (!raw)
866 goto jleave;
868 /* Always requery the attributes, in case we've been moved from background
869 * to foreground or however else in between sessions */
870 /* XXX Always enforce ECHO and ICANON in the OLD attributes - do so as long
871 * XXX as we don't handle terminal stuff when starting commands and don't
872 * XXX properly deal with TTIN and TTOU from all that */
873 tcgetattr(STDIN_FILENO, tiosp);
874 tiosp->c_lflag |= ECHO | ICANON;
876 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
877 tiosp = &_ncl_tios.tnew;
878 tiosp->c_cc[VMIN] = 1;
879 tiosp->c_cc[VTIME] = 0;
880 tiosp->c_iflag &= ~(ISTRIP);
881 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
882 jleave:
883 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
884 NYD2_LEAVE;
887 static void
888 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
890 size_t i;
891 NYD2_ENTER;
893 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
894 if (i >= *l->x_bufsize) {
895 i <<= 1;
896 *l->x_bufsize = i;
897 l->line.cbuf =
898 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
900 NYD2_LEAVE;
903 static void
904 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
906 size_t j;
907 NYD2_ENTER;
909 if (i > 0)
910 memmove(cap, cap + 1, i * sizeof(*cap));
912 /* And.. the (rest of the) visual update */
913 for (j = 0; j < i; ++j)
914 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
915 fputs(" \b", stdout);
916 for (j = 0; j < i; ++j)
917 putchar('\b');
918 NYD2_LEAVE;
921 static ssize_t
922 _ncl_wboundary(struct line *l, ssize_t dir)
924 size_t c, t, i;
925 struct cell *cap;
926 bool_t anynon;
927 NYD2_ENTER;
929 c = l->cursor;
930 t = l->topins;
931 i = (size_t)-1;
933 if (dir < 0) {
934 if (c == 0)
935 goto jleave;
936 } else if (c == t)
937 goto jleave;
938 else
939 --t, --c; /* Unsigned wrapping may occur (twice), then */
941 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
942 wchar_t wc = cap[c + dir].wc;
943 if (iswblank(wc) || iswpunct(wc)) {
944 if (anynon)
945 break;
946 } else
947 anynon = TRU1;
948 ++i;
949 c += dir;
950 if (dir < 0) {
951 if (c == 0)
952 break;
953 } else if (c == t)
954 break;
956 jleave:
957 NYD2_LEAVE;
958 return (ssize_t)i;
961 static ssize_t
962 _ncl_cell2dat(struct line *l)
964 size_t len = 0, i;
965 NYD2_ENTER;
967 if (l->topins > 0)
968 for (i = 0; i < l->topins; ++i) {
969 struct cell *cap = l->line.cells + i;
970 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
971 len += cap->count;
973 l->line.cbuf[len] = '\0';
974 NYD2_LEAVE;
975 return (ssize_t)len;
978 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
979 static void
980 _ncl_cell2save(struct line *l)
982 size_t len, i;
983 struct cell *cap;
984 NYD2_ENTER;
986 l->savec.s = NULL, l->savec.l = 0;
987 if (l->topins == 0)
988 goto jleave;
990 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
991 len += cap->count;
993 l->savec.l = len;
994 l->savec.s = salloc(len + 1);
996 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
997 memcpy(l->savec.s + len, cap->cbuf, cap->count);
998 len += cap->count;
1000 l->savec.s[len] = '\0';
1001 jleave:
1002 NYD2_LEAVE;
1004 # endif
1006 static void
1007 _ncl_khome(struct line *l, bool_t dobell)
1009 size_t c;
1010 NYD2_ENTER;
1012 c = l->cursor;
1013 if (c > 0) {
1014 l->cursor = 0;
1015 while (c-- != 0)
1016 putchar('\b');
1017 } else if (dobell)
1018 putchar('\a');
1019 NYD2_LEAVE;
1022 static void
1023 _ncl_kend(struct line *l)
1025 ssize_t i;
1026 NYD2_ENTER;
1028 i = (ssize_t)(l->topins - l->cursor);
1030 if (i > 0) {
1031 l->cursor = l->topins;
1032 while (i-- != 0)
1033 fputs(l->nd, stdout);
1034 } else
1035 putchar('\a');
1036 NYD2_LEAVE;
1039 static void
1040 _ncl_kbs(struct line *l)
1042 ssize_t c, t;
1043 NYD2_ENTER;
1045 c = l->cursor;
1046 t = l->topins;
1048 if (c > 0) {
1049 putchar('\b');
1050 l->cursor = --c;
1051 l->topins = --t;
1052 t -= c;
1053 _ncl_bs_eof_dvup(l->line.cells + c, t);
1054 } else
1055 putchar('\a');
1056 NYD2_LEAVE;
1059 static void
1060 _ncl_kkill(struct line *l, bool_t dobell)
1062 size_t j, c, i;
1063 NYD2_ENTER;
1065 c = l->cursor;
1066 i = (size_t)(l->topins - c);
1068 if (i > 0) {
1069 l->topins = c;
1070 for (j = i; j != 0; --j)
1071 putchar(' ');
1072 for (j = i; j != 0; --j)
1073 putchar('\b');
1074 } else if (dobell)
1075 putchar('\a');
1076 NYD2_LEAVE;
1079 static ssize_t
1080 _ncl_keof(struct line *l)
1082 size_t c, t;
1083 ssize_t i;
1084 NYD2_ENTER;
1086 c = l->cursor;
1087 t = l->topins;
1088 i = (ssize_t)(t - c);
1090 if (i > 0) {
1091 l->topins = --t;
1092 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1093 } else if (t == 0 /*&& !ok_blook(ignoreeof)*/) {
1094 /*fputs("^D", stdout);
1095 fflush(stdout);*/
1096 i = -1;
1097 /*} else {
1098 putchar('\a');
1099 i = 0;*/
1101 NYD2_LEAVE;
1102 return i;
1105 static void
1106 _ncl_kleft(struct line *l)
1108 NYD2_ENTER;
1109 if (l->cursor > 0) {
1110 --l->cursor;
1111 putchar('\b');
1112 } else
1113 putchar('\a');
1114 NYD2_LEAVE;
1117 static void
1118 _ncl_kright(struct line *l)
1120 NYD2_ENTER;
1121 if (l->cursor < l->topins) {
1122 ++l->cursor;
1123 fputs(l->nd, stdout);
1124 } else
1125 putchar('\a');
1126 NYD2_LEAVE;
1129 static void
1130 _ncl_krefresh(struct line *l)
1132 struct cell *cap;
1133 size_t i;
1134 NYD2_ENTER;
1136 putchar('\r');
1137 if (l->prompt != NULL && *l->prompt != '\0')
1138 fputs(l->prompt, stdout);
1139 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1140 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1141 for (i = l->topins - l->cursor; i > 0; --i)
1142 putchar('\b');
1143 NYD2_LEAVE;
1146 static void
1147 _ncl_kbwddelw(struct line *l)
1149 ssize_t i;
1150 size_t c, t, j;
1151 struct cell *cap;
1152 NYD2_ENTER;
1154 i = _ncl_wboundary(l, -1);
1155 if (i <= 0) {
1156 if (i < 0)
1157 putchar('\a');
1158 goto jleave;
1161 c = l->cursor - i;
1162 t = l->topins;
1163 l->topins = t - i;
1164 l->cursor = c;
1165 cap = l->line.cells + c;
1167 if (t != l->cursor) {
1168 j = t - c + i;
1169 memmove(cap, cap + i, j * sizeof(*cap));
1172 for (j = i; j > 0; --j)
1173 putchar('\b');
1174 for (j = l->topins - c; j > 0; ++cap, --j)
1175 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1176 for (j = i; j > 0; --j)
1177 putchar(' ');
1178 for (j = t - c; j > 0; --j)
1179 putchar('\b');
1180 jleave:
1181 NYD2_LEAVE;
1184 static void
1185 _ncl_kgow(struct line *l, ssize_t dir)
1187 ssize_t i;
1188 NYD2_ENTER;
1190 i = _ncl_wboundary(l, dir);
1191 if (i <= 0) {
1192 if (i < 0)
1193 putchar('\a');
1194 goto jleave;
1197 if (dir < 0) {
1198 l->cursor -= i;
1199 while (i-- > 0)
1200 putchar('\b');
1201 } else {
1202 l->cursor += i;
1203 while (i-- > 0)
1204 fputs(l->nd, stdout);
1206 jleave:
1207 NYD2_LEAVE;
1210 static void
1211 _ncl_kother(struct line *l, wchar_t wc)
1213 /* Append if at EOL, insert otherwise;
1214 * since we may move around character-wise, always use a fresh ps */
1215 mbstate_t ps;
1216 struct cell cell, *cap;
1217 size_t i, c;
1218 NYD2_ENTER;
1220 /* First init a cell and see wether we'll really handle this wc */
1221 cell.wc = wc;
1222 memset(&ps, 0, sizeof ps);
1223 i = wcrtomb(cell.cbuf, wc, &ps);
1224 if (i > MB_LEN_MAX)
1225 goto jleave;
1226 cell.count = (ui32_t)i;
1227 if (options & OPT_ENC_MBSTATE) {
1228 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1229 if (i == 1)
1231 else if (--i < MB_LEN_MAX)
1232 cell.count += (ui32_t)i;
1233 else
1234 goto jleave;
1237 /* Yes, we will! Place it in the array */
1238 c = l->cursor++;
1239 i = l->topins++ - c;
1240 cap = l->line.cells + c;
1241 if (i > 0)
1242 memmove(cap + 1, cap, i * sizeof(cell));
1243 memcpy(cap, &cell, sizeof cell);
1245 /* And update visual */
1246 c = i;
1248 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1249 while ((++cap, i-- != 0));
1250 while (c-- != 0)
1251 putchar('\b');
1252 jleave:
1253 NYD2_LEAVE;
1256 # ifdef HAVE_HISTORY
1257 static size_t
1258 __ncl_khist_shared(struct line *l, struct hist *hp)
1260 size_t rv;
1261 NYD2_ENTER;
1263 if ((l->hist = hp) != NULL) {
1264 l->defc.s = savestrbuf(hp->dat, hp->len);
1265 rv =
1266 l->defc.l = hp->len;
1267 if (l->topins > 0) {
1268 _ncl_khome(l, FAL0);
1269 _ncl_kkill(l, FAL0);
1271 } else {
1272 putchar('\a');
1273 rv = 0;
1275 NYD2_LEAVE;
1276 return rv;
1279 static size_t
1280 _ncl_khist(struct line *l, bool_t backwd)
1282 struct hist *hp;
1283 size_t rv;
1284 NYD2_ENTER;
1286 /* If we're not in history mode yet, save line content;
1287 * also, disallow forward search, then, and, of course, bail unless we
1288 * do have any history at all */
1289 if ((hp = l->hist) == NULL) {
1290 if (!backwd)
1291 goto jleave;
1292 if ((hp = _ncl_hist) == NULL)
1293 goto jleave;
1294 _ncl_cell2save(l);
1295 goto jleave;
1298 hp = backwd ? hp->older : hp->younger;
1299 jleave:
1300 rv = __ncl_khist_shared(l, hp);
1301 NYD2_LEAVE;
1302 return rv;
1305 static size_t
1306 _ncl_krhist(struct line *l)
1308 struct str orig_savec;
1309 struct hist *hp = NULL;
1310 size_t rv;
1311 NYD2_ENTER;
1313 /* We cannot complete an empty line */
1314 if (l->topins == 0) {
1315 /* XXX The upcoming hard reset would restore a set savec buffer,
1316 * XXX so forcefully reset that. A cleaner solution would be to
1317 * XXX reset it whenever a restore is no longer desired */
1318 l->savec.s = NULL, l->savec.l = 0;
1319 goto jleave;
1321 if ((hp = l->hist) == NULL) {
1322 if ((hp = _ncl_hist) == NULL)
1323 goto jleave;
1324 orig_savec.s = NULL;
1325 orig_savec.l = 0; /* silence CC */
1326 } else if ((hp = hp->older) == NULL)
1327 goto jleave;
1328 else
1329 orig_savec = l->savec;
1331 if (orig_savec.s == NULL)
1332 _ncl_cell2save(l);
1333 for (; hp != NULL; hp = hp->older)
1334 if (is_prefix(l->savec.s, hp->dat))
1335 break;
1336 if (orig_savec.s != NULL)
1337 l->savec = orig_savec;
1338 jleave:
1339 rv = __ncl_khist_shared(l, hp);
1340 NYD2_LEAVE;
1341 return rv;
1343 # endif
1345 # ifdef HAVE_TABEXPAND
1346 static size_t
1347 _ncl_kht(struct line *l)
1349 struct str orig, bot, topp, sub, exp;
1350 struct cell *cword, *ctop, *cx;
1351 bool_t set_savec = FAL0;
1352 size_t rv = 0;
1353 NYD2_ENTER;
1355 /* We cannot expand an empty line */
1356 if (l->topins == 0)
1357 goto jleave;
1359 /* Get plain line data; if this is the first expansion/xy, update the
1360 * very original content so that ^G gets the origin back */
1361 orig = l->savec;
1362 _ncl_cell2save(l);
1363 exp = l->savec;
1364 if (orig.s != NULL)
1365 l->savec = orig;
1366 else
1367 set_savec = TRU1;
1368 orig = exp;
1370 cword = l->line.cells;
1371 ctop = cword + l->cursor;
1373 /* topp: separate data right of cursor */
1374 if ((cx = cword + l->topins) != ctop) {
1375 for (rv = 0; cx > ctop; --cx)
1376 rv += cx->count;
1377 topp.l = rv;
1378 topp.s = orig.s + orig.l - rv;
1379 } else
1380 topp.s = NULL, topp.l = 0;
1382 /* bot, sub: we cannot expand the entire data left of cursor, but only
1383 * the last "word", so separate them */
1384 while (cx > cword && !iswspace(cx[-1].wc))
1385 --cx;
1386 for (rv = 0; cword < cx; ++cword)
1387 rv += cword->count;
1388 sub =
1389 bot = orig;
1390 bot.l = rv;
1391 sub.s += rv;
1392 sub.l -= rv;
1393 sub.l -= topp.l;
1395 /* Leave room for "implicit asterisk" expansion, as below */
1396 if (sub.l == 0) {
1397 sub.s = UNCONST("*");
1398 sub.l = 1;
1399 } else {
1400 exp.s = salloc(sub.l + 1 +1);
1401 memcpy(exp.s, sub.s, sub.l);
1402 exp.s[sub.l] = '\0';
1403 sub.s = exp.s;
1406 /* TODO there is a TODO note upon fexpand() with multi-return;
1407 * TODO if that will change, the if() below can be simplified */
1408 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1409 jredo:
1410 hold_all_sigs();
1411 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1412 rele_all_sigs();
1414 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1415 goto jnope;
1416 /* If the expansion equals the original string, assume the user wants what
1417 * is usually known as tab completion, append `*' and restart */
1418 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1419 if (sub.s[sub.l - 1] == '*')
1420 goto jnope;
1421 sub.s[sub.l++] = '*';
1422 sub.s[sub.l] = '\0';
1423 goto jredo;
1426 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1427 * Take care to take *prompt* into account, since we don't know
1428 * anything about it's visual length (fputs(3) is used), simply
1429 * assume each character requires two columns */
1430 /* TODO the problem is that we loose control otherwise; in the best
1431 * TODO case the user can control via ^A and ^K etc., but be safe;
1432 * TODO we cannot simply adjust fexpand() because we don't know how
1433 * TODO that is implemented... The real solution would be to check
1434 * TODO wether we fit on a line, and start a pager if not.
1435 * TODO However, that should be part of a real tab-COMPLETION, then,
1436 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1437 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1438 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1439 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1440 exp.s = UNCONST("[ERR_TOO_LONG]");
1441 exp.l = sizeof("[ERR_TOO_LONG]") - 1;
1442 topp.l = 0;
1443 if (rv + bot.l + exp.l >= MAX_INPUT)
1444 bot.l = 0;
1445 if (rv + exp.l >= MAX_INPUT) {
1446 exp.s = UNCONST("[ERR]");
1447 exp.l = sizeof("[ERR]") - 1;
1451 orig.l = bot.l + exp.l + topp.l;
1452 orig.s = salloc(orig.l + 5 +1);
1453 if ((rv = bot.l) > 0)
1454 memcpy(orig.s, bot.s, rv);
1455 memcpy(orig.s + rv, exp.s, exp.l);
1456 rv += exp.l;
1457 if (topp.l > 0) {
1458 memcpy(orig.s + rv, topp.s, topp.l);
1459 rv += topp.l;
1461 orig.s[rv] = '\0';
1463 l->defc = orig;
1464 _ncl_khome(l, FAL0);
1465 _ncl_kkill(l, FAL0);
1466 jleave:
1467 NYD2_LEAVE;
1468 return rv;
1469 jnope:
1470 /* If we've provided a default content, but failed to expand, there is
1471 * nothing we can "revert to": drop that default again */
1472 if (set_savec)
1473 l->savec.s = NULL, l->savec.l = 0;
1474 rv = 0;
1475 goto jleave;
1477 # endif /* HAVE_TABEXPAND */
1479 static ssize_t
1480 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1481 SMALLOC_DEBUG_ARGS)
1483 /* We want to save code, yet we may have to incorporate a lines'
1484 * default content and / or default input to switch back to after some
1485 * history movement; let "len > 0" mean "have to display some data
1486 * buffer", and only otherwise read(2) it */
1487 mbstate_t ps[2];
1488 struct line l;
1489 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp, cursor_maybe, cursor_store;
1490 wchar_t wc;
1491 ssize_t rv;
1492 NYD_ENTER;
1494 memset(&l, 0, sizeof l);
1495 l.line.cbuf = *buf;
1496 if (len != 0) {
1497 l.defc.s = savestrbuf(*buf, len);
1498 l.defc.l = len;
1500 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1501 l.prompt = prompt = "?ERR?";
1502 /* TODO *l.nd=='\0' : instead adjust accmacvar.c to disallow empty vals */
1503 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1504 l.nd = "\033[C"; /* XXX no "magic" constant */
1505 l.x_buf = buf;
1506 l.x_bufsize = bufsize;
1508 if (prompt != NULL && *prompt != '\0')
1509 fputs(prompt, stdout);
1510 fflush(stdout);
1512 jrestart:
1513 memset(ps, 0, sizeof ps);
1514 cursor_maybe = cursor_store = 0;
1515 /* TODO: NCL: we should output the reset sequence when we jrestart:
1516 * TODO: NCL: if we are using a stateful encoding? !
1517 * TODO: NCL: in short: this is not yet well understood */
1518 for (;;) {
1519 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1521 /* Normal read(2)? Else buffer-takeover: speed this one up */
1522 if (len == 0)
1523 cbufp =
1524 cbuf = cbuf_base;
1525 else {
1526 assert(l.defc.l > 0 && l.defc.s != NULL);
1527 cbufp =
1528 cbuf = l.defc.s + (l.defc.l - len);
1529 cbufp += len;
1532 /* Read in the next complete multibyte character */
1533 for (;;) {
1534 if (len == 0) {
1535 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1536 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1537 continue;
1538 goto jleave;
1540 ++cbufp;
1543 /* Ach! the ISO C multibyte handling!
1544 * Encodings with locking shift states cannot really be helped, since
1545 * it is impossible to only query the shift state, as opposed to the
1546 * entire shift state + character pair (via ISO C functions) */
1547 rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), ps + 0);
1548 if (rv <= 0) {
1549 /* Any error during take-over can only result in a hard reset;
1550 * Otherwise, if it's a hard error, or if too many redundant shift
1551 * sequences overflow our buffer, also perform a hard reset */
1552 if (len != 0 || rv == -1 ||
1553 sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)) {
1554 l.savec.s = l.defc.s = NULL,
1555 l.savec.l = l.defc.l = len = 0;
1556 putchar('\a');
1557 wc = 'G';
1558 goto jreset;
1560 /* Otherwise, due to the way we deal with the buffer, we need to
1561 * restore the mbstate_t from before this conversion */
1562 ps[0] = ps[1];
1563 continue;
1566 if (len != 0 && (len -= (size_t)rv) == 0)
1567 l.defc.s = NULL, l.defc.l = 0;
1568 ps[1] = ps[0];
1569 break;
1572 /* Don't interpret control bytes during buffer take-over */
1573 if (cbuf != cbuf_base)
1574 goto jprint;
1575 switch (wc) {
1576 case 'A' ^ 0x40: /* cursor home */
1577 _ncl_khome(&l, TRU1);
1578 break;
1579 case 'B' ^ 0x40: /* backward character */
1580 j_b:
1581 _ncl_kleft(&l);
1582 break;
1583 /* 'C': interrupt (CTRL-C) */
1584 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1585 if ((rv = _ncl_keof(&l)) < 0)
1586 goto jleave;
1587 break;
1588 case 'E' ^ 0x40: /* end of line */
1589 _ncl_kend(&l);
1590 break;
1591 case 'F' ^ 0x40: /* forward character */
1592 j_f:
1593 _ncl_kright(&l);
1594 break;
1595 /* 'G' below */
1596 case 'H' ^ 0x40: /* backspace */
1597 case '\177':
1598 _ncl_kbs(&l);
1599 break;
1600 case 'I' ^ 0x40: /* horizontal tab */
1601 # ifdef HAVE_TABEXPAND
1602 if ((len = _ncl_kht(&l)) > 0)
1603 goto jrestart;
1604 # endif
1605 goto jbell;
1606 case 'J' ^ 0x40: /* NL (\n) */
1607 goto jdone;
1608 case 'G' ^ 0x40: /* full reset */
1609 jreset:
1610 /* FALLTHRU */
1611 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1612 _ncl_khome(&l, FAL0);
1613 /* FALLTHRU */
1614 case 'K' ^ 0x40: /* kill from cursor to end of line */
1615 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1616 /* (Handle full reset?) */
1617 if (wc == ('G' ^ 0x40)) {
1618 # ifdef HAVE_HISTORY
1619 l.hist = NULL;
1620 # endif
1621 if ((len = l.savec.l) != 0) {
1622 l.defc = l.savec;
1623 l.savec.s = NULL, l.savec.l = 0;
1624 } else
1625 len = l.defc.l;
1627 fflush(stdout);
1628 goto jrestart;
1629 case 'L' ^ 0x40: /* repaint line */
1630 j_l:
1631 _ncl_krefresh(&l);
1632 break;
1633 /* 'M': CR (\r) */
1634 case 'N' ^ 0x40: /* history next */
1635 j_n:
1636 # ifdef HAVE_HISTORY
1637 if (l.hist == NULL)
1638 goto jbell;
1639 if ((len = _ncl_khist(&l, FAL0)) > 0)
1640 goto jrestart;
1641 wc = 'G' ^ 0x40;
1642 goto jreset;
1643 # else
1644 goto jbell;
1645 # endif
1646 /* 'O' */
1647 case 'O' ^ 0x40: /* `dp' */
1648 putchar('\n');
1649 cbuf_base[0] = 'd';
1650 cbuf_base[1] = 'p';
1651 cbuf_base[2] = '\0';
1652 pstate &= ~PS_HOOK_MASK;
1653 execute(cbuf_base, 2);
1654 goto j_l;
1655 case 'P' ^ 0x40: /* history previous */
1656 j_p:
1657 # ifdef HAVE_HISTORY
1658 if ((len = _ncl_khist(&l, TRU1)) > 0)
1659 goto jrestart;
1660 wc = 'G' ^ 0x40;
1661 goto jreset;
1662 # else
1663 goto jbell;
1664 # endif
1665 /* 'Q': no code */
1666 case 'R' ^ 0x40: /* reverse history search */
1667 # ifdef HAVE_HISTORY
1668 if ((len = _ncl_krhist(&l)) > 0)
1669 goto jrestart;
1670 wc = 'G' ^ 0x40;
1671 goto jreset;
1672 # else
1673 goto jbell;
1674 # endif
1675 /* 'S': no code */
1676 /* 'U' above */
1677 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1678 case 'W' ^ 0x40: /* backward delete "word" */
1679 _ncl_kbwddelw(&l);
1680 break;
1681 case 'X' ^ 0x40: /* move cursor forward "word" */
1682 _ncl_kgow(&l, +1);
1683 break;
1684 case 'Y' ^ 0x40: /* move cursor backward "word" */
1685 _ncl_kgow(&l, -1);
1686 break;
1687 /* 'Z': suspend (CTRL-Z) */
1688 case 0x1B:
1689 if (cursor_maybe++ != 0)
1690 goto jreset;
1691 continue;
1692 default:
1693 /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */
1694 if (cursor_maybe > 0) {
1695 if (++cursor_maybe == 2) {
1696 if (wc == L'[')
1697 continue;
1698 cursor_maybe = 0;
1699 } else if (cursor_maybe == 3) {
1700 cursor_maybe = 0;
1701 switch (wc) {
1702 default: break;
1703 case L'A': goto j_p;
1704 case L'B': goto j_n;
1705 case L'C': goto j_f;
1706 case L'D': goto j_b;
1707 case L'H':
1708 cursor_store = '0';
1709 goto J_xterm_noapp;
1710 case L'F':
1711 cursor_store = '$';
1712 goto J_xterm_noapp;
1713 case L'1':
1714 case L'4':
1715 case L'5':
1716 case L'6':
1717 cursor_store = ((wc == L'1') ? '0' :
1718 (wc == L'4' ? '$' : (wc == L'5' ? '-' : '+')));
1719 cursor_maybe = 3;
1720 continue;
1722 _ncl_kother(&l, L'[');
1723 } else {
1724 cursor_maybe = 0;
1725 if (wc == L'~')
1726 J_xterm_noapp: {
1727 char x[2];
1728 x[0] = cursor_store;
1729 x[1] = '\0';
1730 putchar('\n');
1731 c_scroll(x);
1732 cursor_store = 0;
1733 goto j_l;
1735 _ncl_kother(&l, L'[');
1736 _ncl_kother(&l, (wchar_t)cursor_store);
1737 cursor_store = 0;
1740 jprint:
1741 if (iswprint(wc)) {
1742 _ncl_kother(&l, wc);
1743 /* Don't clear the history during takeover..
1744 * ..and also avoid fflush()ing unless we've worked entire buffer */
1745 if (len > 0)
1746 continue;
1747 # ifdef HAVE_HISTORY
1748 if (cbuf == cbuf_base)
1749 l.hist = NULL;
1750 # endif
1751 } else {
1752 jbell:
1753 putchar('\a');
1755 break;
1757 fflush(stdout);
1760 /* We have a completed input line, convert the struct cell data to its
1761 * plain character equivalent */
1762 jdone:
1763 putchar('\n');
1764 fflush(stdout);
1765 len = _ncl_cell2dat(&l);
1766 rv = (ssize_t)len;
1767 jleave:
1768 NYD_LEAVE;
1769 return rv;
1772 FL void
1773 tty_init(void)
1775 # ifdef HAVE_HISTORY
1776 long hs;
1777 char *v, *lbuf;
1778 FILE *f;
1779 size_t lsize, cnt, llen;
1780 # endif
1781 NYD_ENTER;
1783 # ifdef HAVE_HISTORY
1784 _CL_HISTSIZE(hs);
1785 _ncl_hist_size = 0;
1786 _ncl_hist_size_max = hs;
1787 if (hs == 0)
1788 goto jleave;
1790 _CL_HISTFILE(v);
1791 if (v == NULL)
1792 goto jleave;
1794 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1795 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1796 if (f == NULL)
1797 goto jdone;
1798 (void)file_lock(fileno(f), FLT_READ, 0,0, 500);
1800 lbuf = NULL;
1801 lsize = 0;
1802 cnt = fsize(f);
1803 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1804 if (llen > 0 && lbuf[llen - 1] == '\n')
1805 lbuf[--llen] = '\0';
1806 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1807 continue;
1808 else {
1809 bool_t isgabby = (lbuf[0] == '*');
1810 _ncl_hist_load = TRU1;
1811 tty_addhist(lbuf + isgabby, isgabby);
1812 _ncl_hist_load = FAL0;
1815 if (lbuf != NULL)
1816 free(lbuf);
1818 fclose(f);
1819 jdone:
1820 rele_all_sigs(); /* XXX remove jumps */
1821 jleave:
1822 # endif /* HAVE_HISTORY */
1823 NYD_LEAVE;
1826 FL void
1827 tty_destroy(void)
1829 # ifdef HAVE_HISTORY
1830 long hs;
1831 char *v;
1832 struct hist *hp;
1833 bool_t dogabby;
1834 FILE *f;
1835 # endif
1836 NYD_ENTER;
1838 # ifdef HAVE_HISTORY
1839 _CL_HISTSIZE(hs);
1840 if (hs == 0)
1841 goto jleave;
1842 _CL_HISTFILE(v);
1843 if (v == NULL)
1844 goto jleave;
1846 dogabby = ok_blook(history_gabby_persist);
1848 if ((hp = _ncl_hist) != NULL)
1849 for (; hp->older != NULL; hp = hp->older)
1850 if ((dogabby || !hp->isgabby) && --hs == 0)
1851 break;
1853 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1854 f = fopen(v, "w"); /* TODO temporary + rename?! */
1855 if (f == NULL)
1856 goto jdone;
1857 (void)file_lock(fileno(f), FLT_WRITE, 0,0, 500);
1858 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1859 goto jclose;
1861 for (; hp != NULL; hp = hp->younger) {
1862 if (!hp->isgabby || dogabby) {
1863 if (hp->isgabby)
1864 putc('*', f);
1865 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1866 putc('\n', f);
1869 jclose:
1870 fclose(f);
1871 jdone:
1872 rele_all_sigs(); /* XXX remove jumps */
1873 jleave:
1874 # endif /* HAVE_HISTORY */
1875 NYD_LEAVE;
1878 FL void
1879 tty_signal(int sig)
1881 sigset_t nset, oset;
1882 NYD_X; /* Signal handler */
1884 switch (sig) {
1885 case SIGWINCH:
1886 /* We don't deal with SIGWINCH, yet get called from main.c */
1887 break;
1888 default:
1889 _ncl_term_mode(FAL0);
1890 _ncl_sigs_down();
1891 sigemptyset(&nset);
1892 sigaddset(&nset, sig);
1893 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1894 n_raise(sig);
1895 /* When we come here we'll continue editing, so reestablish */
1896 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1897 _ncl_sigs_up();
1898 _ncl_term_mode(TRU1);
1899 break;
1903 FL int
1904 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1905 SMALLOC_DEBUG_ARGS)
1907 ssize_t nn;
1908 NYD_ENTER;
1910 /* Of course we have races here, but they cannot be avoided on POSIX
1911 * (except by even *more* actions) */
1912 _ncl_sigs_up();
1913 _ncl_term_mode(TRU1);
1914 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1915 _ncl_term_mode(FAL0);
1916 _ncl_sigs_down();
1917 NYD_LEAVE;
1918 return (int)nn;
1921 FL void
1922 tty_addhist(char const *s, bool_t isgabby)
1924 # ifdef HAVE_HISTORY
1925 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1926 ui32_t l;
1927 struct hist *h, *o, *y;
1928 # endif
1929 NYD_ENTER;
1930 UNUSED(s);
1931 UNUSED(isgabby);
1933 # ifdef HAVE_HISTORY
1934 if (isgabby && !ok_blook(history_gabby))
1935 goto j_leave;
1936 if (_ncl_hist_size_max == 0)
1937 goto j_leave;
1938 _CL_CHECK_ADDHIST(s, goto j_leave);
1940 l = (ui32_t)strlen(s);
1942 /* Eliminating duplicates is expensive, but simply inacceptable so
1943 * during the load of a potentially large history file! */
1944 if (!_ncl_hist_load)
1945 for (h = _ncl_hist; h != NULL; h = h->older)
1946 if (h->len == l && !strcmp(h->dat, s)) {
1947 hold_all_sigs(); /* TODO */
1948 if (h->isgabby)
1949 h->isgabby = !!isgabby;
1950 o = h->older;
1951 y = h->younger;
1952 if (o != NULL)
1953 o->younger = y;
1954 else
1955 _ncl_hist_tail = y;
1956 if (y != NULL)
1957 y->older = o;
1958 else
1959 _ncl_hist = o;
1960 goto jleave;
1962 hold_all_sigs();
1964 ++_ncl_hist_size;
1965 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1966 --_ncl_hist_size;
1967 if ((h = _ncl_hist_tail) != NULL) {
1968 if ((_ncl_hist_tail = h->younger) == NULL)
1969 _ncl_hist = NULL;
1970 else
1971 _ncl_hist_tail->older = NULL;
1972 free(h);
1976 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1977 h->isgabby = !!isgabby;
1978 h->len = l;
1979 memcpy(h->dat, s, l +1);
1980 jleave:
1981 if ((h->older = _ncl_hist) != NULL)
1982 _ncl_hist->younger = h;
1983 else
1984 _ncl_hist_tail = h;
1985 h->younger = NULL;
1986 _ncl_hist = h;
1988 rele_all_sigs();
1989 j_leave:
1990 # endif
1991 NYD_LEAVE;
1994 # ifdef HAVE_HISTORY
1995 FL int
1996 c_history(void *v)
1998 C_HISTORY_SHARED;
2000 jlist: {
2001 FILE *fp;
2002 size_t i, b;
2003 struct hist *h;
2005 if (_ncl_hist == NULL)
2006 goto jleave;
2008 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
2009 NULL) {
2010 n_perr(_("tmpfile"), 0);
2011 v = NULL;
2012 goto jleave;
2015 i = _ncl_hist_size;
2016 b = 0;
2017 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
2018 fprintf(fp,
2019 "%c%4" PRIuZ ". %-50.50s (%4" PRIuZ "+%2" PRIu32 " bytes)\n",
2020 (h->isgabby ? '*' : ' '), i, h->dat, b, h->len);
2022 page_or_print(fp, i);
2023 Fclose(fp);
2025 goto jleave;
2027 jclear: {
2028 struct hist *h;
2030 while ((h = _ncl_hist) != NULL) {
2031 _ncl_hist = h->older;
2032 free(h);
2034 _ncl_hist_tail = NULL;
2035 _ncl_hist_size = 0;
2037 goto jleave;
2039 jentry: {
2040 struct hist *h;
2042 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2043 entry = (long)_ncl_hist_size - entry;
2044 for (h = _ncl_hist;; h = h->older)
2045 if (h == NULL)
2046 break;
2047 else if (entry-- != 0)
2048 continue;
2049 else {
2050 v = temporary_arg_v_store = h->dat;
2051 goto jleave;
2054 v = NULL;
2056 goto jleave;
2058 # endif /* HAVE_HISTORY */
2059 #endif /* HAVE_NCL */
2062 * The really-nothing-at-all implementation
2065 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2066 FL void
2067 tty_init(void)
2069 NYD_ENTER;
2070 NYD_LEAVE;
2073 FL void
2074 tty_destroy(void)
2076 NYD_ENTER;
2077 NYD_LEAVE;
2080 FL void
2081 tty_signal(int sig)
2083 NYD_X; /* Signal handler */
2084 UNUSED(sig);
2087 FL int
2088 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2089 SMALLOC_DEBUG_ARGS)
2091 int rv;
2092 NYD_ENTER;
2094 if (prompt != NULL) {
2095 if (*prompt != '\0')
2096 fputs(prompt, stdout);
2097 fflush(stdout);
2099 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2100 NYD_LEAVE;
2101 return rv;
2104 FL void
2105 tty_addhist(char const *s, bool_t isgabby)
2107 NYD_ENTER;
2108 UNUSED(s);
2109 UNUSED(isgabby);
2110 NYD_LEAVE;
2112 #endif /* nothing at all */
2114 /* s-it-mode */