nail.1, COPYING: remove Open Group reference (no text included?)
[s-mailx.git] / tty.c
blob67b4fa303f777090e9cf3f86c8573851be8c1cad
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)) == NULL) {
431 n_perr(_("tmpfile"), 0);
432 v = NULL;
433 goto jleave;
436 hs = history_get_history_state();
438 for (i = (ul_i)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
439 char *cp = (*--hl)->line;
440 size_t sl = strlen(cp);
441 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n", i, cp, b, sl);
442 b += sl;
445 page_or_print(fp, (size_t)hs->length);
446 Fclose(fp);
448 goto jleave;
450 jclear:
451 clear_history();
452 goto jleave;
454 jentry: {
455 HISTORY_STATE *hs = history_get_history_state();
457 if (UICMP(z, entry, <=, hs->length))
458 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
459 else
460 v = NULL;
462 goto jleave;
464 # endif /* HAVE_HISTORY */
465 #endif /* HAVE_READLINE */
468 * BSD editline(3)
471 #ifdef HAVE_EDITLINE
472 static EditLine * _el_el; /* editline(3) handle */
473 static char const * _el_prompt; /* Current prompt */
474 # ifdef HAVE_HISTORY
475 static History * _el_hcom; /* History handle for commline */
476 # endif
478 static char const * _el_getprompt(void);
480 static char const *
481 _el_getprompt(void)
483 return _el_prompt;
486 FL void
487 tty_init(void)
489 # ifdef HAVE_HISTORY
490 HistEvent he;
491 long hs;
492 char *v;
493 # endif
494 NYD_ENTER;
496 # ifdef HAVE_HISTORY
497 _CL_HISTSIZE(hs);
498 _el_hcom = history_init();
499 history(_el_hcom, &he, H_SETSIZE, (int)hs);
500 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
501 # endif
503 _el_el = el_init(uagent, stdin, stdout, stderr);
504 el_set(_el_el, EL_SIGNAL, 1);
505 el_set(_el_el, EL_TERMINAL, NULL);
506 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
507 # ifdef HAVE_HISTORY
508 el_set(_el_el, EL_HIST, &history, _el_hcom);
509 # endif
510 el_set(_el_el, EL_EDITOR, "emacs");
511 # ifdef EL_PROMPT_ESC
512 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
513 # else
514 el_set(_el_el, EL_PROMPT, &_el_getprompt);
515 # endif
516 # if 0
517 el_set(_el_el, EL_ADDFN, "tab_complete",
518 "editline(3) internal completion function", &_el_file_cpl);
519 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
520 # endif
521 # ifdef HAVE_HISTORY
522 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
523 # endif
524 el_source(_el_el, NULL); /* Source ~/.editrc */
526 /* Because el_source() may have introduced yet a different history size
527 * limit, simply load and incorporate the history, leave it up to
528 * editline(3) to do the rest */
529 # ifdef HAVE_HISTORY
530 _CL_HISTFILE(v);
531 if (v != NULL)
532 history(_el_hcom, &he, H_LOAD, v);
533 # endif
534 NYD_LEAVE;
537 FL void
538 tty_destroy(void)
540 # ifdef HAVE_HISTORY
541 HistEvent he;
542 char *v;
543 # endif
544 NYD_ENTER;
546 el_end(_el_el);
548 # ifdef HAVE_HISTORY
549 _CL_HISTFILE(v);
550 if (v != NULL)
551 history(_el_hcom, &he, H_SAVE, v);
552 history_end(_el_hcom);
553 # endif
554 NYD_LEAVE;
557 FL void
558 tty_signal(int sig)
560 NYD_X; /* Signal handler */
561 switch (sig) {
562 # ifdef SIGWINCH
563 case SIGWINCH:
564 el_resize(_el_el);
565 break;
566 # endif
567 default:
568 break;
572 FL int
573 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
574 SMALLOC_DEBUG_ARGS)
576 int nn;
577 char const *line;
578 NYD_ENTER;
580 _el_prompt = (prompt != NULL) ? prompt : "";
581 if (n > 0)
582 el_push(_el_el, *linebuf);
583 line = el_gets(_el_el, &nn);
585 if (line == NULL) {
586 nn = -1;
587 goto jleave;
589 assert(nn >= 0);
590 n = (size_t)nn;
591 if (n > 0 && line[n - 1] == '\n')
592 nn = (int)--n;
594 if (n >= *linesize) {
595 *linesize = LINESIZE + n + 1;
596 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
598 memcpy(*linebuf, line, n);
599 (*linebuf)[n] = '\0';
600 jleave:
601 NYD_LEAVE;
602 return nn;
605 FL void
606 tty_addhist(char const *s, bool_t isgabby)
608 # ifdef HAVE_HISTORY
609 /* Enlarge meaning of unique .. to something that rocks;
610 * xxx unfortunately this is expensive to do with editline(3)
611 * xxx maybe it would be better to hook the ptfs instead? */
612 HistEvent he;
613 int i;
614 # endif
615 NYD_ENTER;
616 UNUSED(s);
617 UNUSED(isgabby);
619 # ifdef HAVE_HISTORY
620 if (isgabby && !ok_blook(history_gabby))
621 goto jleave;
622 _CL_CHECK_ADDHIST(s, goto jleave);
624 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
625 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
626 i = history(_el_hcom, &he, H_NEXT))
627 if (!strcmp(he.str, s)) {
628 history(_el_hcom, &he, H_DEL, he.num);
629 break;
631 history(_el_hcom, &he, H_ENTER, s);
632 rele_all_sigs(); /* XXX remove jumps */
633 jleave:
634 # endif
635 NYD_LEAVE;
638 # ifdef HAVE_HISTORY
639 FL int
640 c_history(void *v)
642 C_HISTORY_SHARED;
644 jlist: {
645 HistEvent he;
646 FILE *fp;
647 size_t i, b;
648 int x;
650 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
651 n_perr(_("tmpfile"), 0);
652 v = NULL;
653 goto jleave;
656 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
657 b = 0;
658 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
659 x = history(_el_hcom, &he, H_NEXT)) {
660 size_t sl = strlen(he.str);
661 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n",
662 (ul_i)i, he.str, (ul_i)b, (ul_i)sl);
663 --i;
664 b += sl;
667 page_or_print(fp, i);
668 Fclose(fp);
670 goto jleave;
672 jclear: {
673 HistEvent he;
674 history(_el_hcom, &he, H_CLEAR);
676 goto jleave;
678 jentry: {
679 HistEvent he;
680 size_t i;
681 int x;
683 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
684 if (UICMP(z, entry, <=, i)) {
685 entry = (long)i - entry;
686 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
687 x = history(_el_hcom, &he, H_NEXT))
688 if (entry-- == 0) {
689 v = temporary_arg_v_store = UNCONST(he.str);
690 goto jleave;
693 v = NULL;
695 goto jleave;
697 # endif /* HAVE_HISTORY */
698 #endif /* HAVE_EDITLINE */
701 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
703 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
704 * We do not handle character widths because the terminal must deal with that
705 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
706 * characters by definition on the other. We're addicted.
708 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
709 * we're forced to use the very same buffer--the one that is passed through to
710 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
711 * convert that on-the-fly back to the plain char* result once we're done.
712 * To simplify our live, use savestr() buffers for all other needed memory
715 #ifdef HAVE_NCL
716 # ifndef MAX_INPUT
717 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
718 # endif
720 /* Since we simply fputs(3) the prompt, assume each character requires two
721 * visual cells -- and we need to restrict the maximum prompt size because
722 * of MAX_INPUT and our desire to have room for some error message left */
723 # define _PROMPT_VLEN(P) (strlen(P) * 2)
724 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
726 struct xtios {
727 struct termios told;
728 struct termios tnew;
731 struct cell {
732 wchar_t wc;
733 ui32_t count;
734 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
737 struct line {
738 size_t cursor; /* Current cursor position */
739 size_t topins; /* Outermost cursor col set */
740 union {
741 char *cbuf; /* *x_buf */
742 struct cell *cells;
743 } line;
744 struct str defc; /* Current default content */
745 struct str savec; /* Saved default content */
746 # ifdef HAVE_HISTORY
747 struct hist *hist; /* History cursor */
748 # endif
749 char const *prompt;
750 char const *nd; /* Cursor right */
751 char **x_buf; /* Caller pointers */
752 size_t *x_bufsize;
755 # ifdef HAVE_HISTORY
756 struct hist {
757 struct hist *older;
758 struct hist *younger;
759 ui32_t isgabby : 1;
760 ui32_t len : 31;
761 char dat[VFIELD_SIZE(sizeof(ui32_t))];
763 # endif
765 static sighandler_type _ncl_oint;
766 static sighandler_type _ncl_oquit;
767 static sighandler_type _ncl_oterm;
768 static sighandler_type _ncl_ohup;
769 static sighandler_type _ncl_otstp;
770 static sighandler_type _ncl_ottin;
771 static sighandler_type _ncl_ottou;
772 static struct xtios _ncl_tios;
773 # ifdef HAVE_HISTORY
774 static struct hist *_ncl_hist;
775 static struct hist *_ncl_hist_tail;
776 static size_t _ncl_hist_size;
777 static size_t _ncl_hist_size_max;
778 static bool_t _ncl_hist_load;
779 # endif
781 static void _ncl_sigs_up(void);
782 static void _ncl_sigs_down(void);
784 static void _ncl_term_mode(bool_t raw);
786 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
787 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
788 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
789 static ssize_t _ncl_cell2dat(struct line *l);
790 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
791 static void _ncl_cell2save(struct line *l);
792 # endif
794 static void _ncl_khome(struct line *l, bool_t dobell);
795 static void _ncl_kend(struct line *l);
796 static void _ncl_kbs(struct line *l);
797 static void _ncl_kkill(struct line *l, bool_t dobell);
798 static ssize_t _ncl_keof(struct line *l);
799 static void _ncl_kleft(struct line *l);
800 static void _ncl_kright(struct line *l);
801 static void _ncl_krefresh(struct line *l);
802 static void _ncl_kbwddelw(struct line *l);
803 static void _ncl_kgow(struct line *l, ssize_t dir);
804 static void _ncl_kother(struct line *l, wchar_t wc);
805 # ifdef HAVE_HISTORY
806 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
807 static size_t _ncl_khist(struct line *l, bool_t backwd);
808 static size_t _ncl_krhist(struct line *l);
809 # endif
810 # ifdef HAVE_TABEXPAND
811 static size_t _ncl_kht(struct line *l);
812 # endif
813 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
814 size_t len SMALLOC_DEBUG_ARGS);
816 static void
817 _ncl_sigs_up(void)
819 sigset_t nset, oset;
820 NYD2_ENTER;
822 sigfillset(&nset);
824 sigprocmask(SIG_BLOCK, &nset, &oset);
825 _ncl_oint = safe_signal(SIGINT, &tty_signal);
826 _ncl_oquit = safe_signal(SIGQUIT, &tty_signal);
827 _ncl_oterm = safe_signal(SIGTERM, &tty_signal);
828 _ncl_ohup = safe_signal(SIGHUP, &tty_signal);
829 _ncl_otstp = safe_signal(SIGTSTP, &tty_signal);
830 _ncl_ottin = safe_signal(SIGTTIN, &tty_signal);
831 _ncl_ottou = safe_signal(SIGTTOU, &tty_signal);
832 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
833 NYD2_LEAVE;
836 static void
837 _ncl_sigs_down(void)
839 sigset_t nset, oset;
840 NYD2_ENTER;
842 sigfillset(&nset);
844 sigprocmask(SIG_BLOCK, &nset, &oset);
845 safe_signal(SIGINT, _ncl_oint);
846 safe_signal(SIGQUIT, _ncl_oquit);
847 safe_signal(SIGTERM, _ncl_oterm);
848 safe_signal(SIGHUP, _ncl_ohup);
849 safe_signal(SIGTSTP, _ncl_otstp);
850 safe_signal(SIGTTIN, _ncl_ottin);
851 safe_signal(SIGTTOU, _ncl_ottou);
852 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
853 NYD2_LEAVE;
856 static void
857 _ncl_term_mode(bool_t raw)
859 struct termios *tiosp;
860 NYD2_ENTER;
862 tiosp = &_ncl_tios.told;
863 if (!raw)
864 goto jleave;
866 /* Always requery the attributes, in case we've been moved from background
867 * to foreground or however else in between sessions */
868 /* XXX Always enforce ECHO and ICANON in the OLD attributes - do so as long
869 * XXX as we don't properly deal with TTIN and TTOU etc. */
870 tcgetattr(STDIN_FILENO, tiosp);
871 tiosp->c_lflag |= ECHO | ICANON;
873 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
874 tiosp = &_ncl_tios.tnew;
875 tiosp->c_cc[VMIN] = 1;
876 tiosp->c_cc[VTIME] = 0;
877 tiosp->c_iflag &= ~(ISTRIP);
878 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
879 jleave:
880 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
881 NYD2_LEAVE;
884 static void
885 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
887 size_t i;
888 NYD2_ENTER;
890 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
891 if (i >= *l->x_bufsize) {
892 i <<= 1;
893 *l->x_bufsize = i;
894 l->line.cbuf =
895 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
897 NYD2_LEAVE;
900 static void
901 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
903 size_t j;
904 NYD2_ENTER;
906 if (i > 0)
907 memmove(cap, cap + 1, i * sizeof(*cap));
909 /* And.. the (rest of the) visual update */
910 for (j = 0; j < i; ++j)
911 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
912 fputs(" \b", stdout);
913 for (j = 0; j < i; ++j)
914 putchar('\b');
915 NYD2_LEAVE;
918 static ssize_t
919 _ncl_wboundary(struct line *l, ssize_t dir)
921 size_t c, t, i;
922 struct cell *cap;
923 bool_t anynon;
924 NYD2_ENTER;
926 c = l->cursor;
927 t = l->topins;
928 i = (size_t)-1;
930 if (dir < 0) {
931 if (c == 0)
932 goto jleave;
933 } else if (c == t)
934 goto jleave;
935 else
936 --t, --c; /* Unsigned wrapping may occur (twice), then */
938 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
939 wchar_t wc = cap[c + dir].wc;
940 if (iswblank(wc) || iswpunct(wc)) {
941 if (anynon)
942 break;
943 } else
944 anynon = TRU1;
945 ++i;
946 c += dir;
947 if (dir < 0) {
948 if (c == 0)
949 break;
950 } else if (c == t)
951 break;
953 jleave:
954 NYD2_LEAVE;
955 return (ssize_t)i;
958 static ssize_t
959 _ncl_cell2dat(struct line *l)
961 size_t len = 0, i;
962 NYD2_ENTER;
964 if (l->topins > 0)
965 for (i = 0; i < l->topins; ++i) {
966 struct cell *cap = l->line.cells + i;
967 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
968 len += cap->count;
970 l->line.cbuf[len] = '\0';
971 NYD2_LEAVE;
972 return (ssize_t)len;
975 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
976 static void
977 _ncl_cell2save(struct line *l)
979 size_t len, i;
980 struct cell *cap;
981 NYD2_ENTER;
983 l->savec.s = NULL, l->savec.l = 0;
984 if (l->topins == 0)
985 goto jleave;
987 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
988 len += cap->count;
990 l->savec.l = len;
991 l->savec.s = salloc(len + 1);
993 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
994 memcpy(l->savec.s + len, cap->cbuf, cap->count);
995 len += cap->count;
997 l->savec.s[len] = '\0';
998 jleave:
999 NYD2_LEAVE;
1001 # endif
1003 static void
1004 _ncl_khome(struct line *l, bool_t dobell)
1006 size_t c;
1007 NYD2_ENTER;
1009 c = l->cursor;
1010 if (c > 0) {
1011 l->cursor = 0;
1012 while (c-- != 0)
1013 putchar('\b');
1014 } else if (dobell)
1015 putchar('\a');
1016 NYD2_LEAVE;
1019 static void
1020 _ncl_kend(struct line *l)
1022 ssize_t i;
1023 NYD2_ENTER;
1025 i = (ssize_t)(l->topins - l->cursor);
1027 if (i > 0) {
1028 l->cursor = l->topins;
1029 while (i-- != 0)
1030 fputs(l->nd, stdout);
1031 } else
1032 putchar('\a');
1033 NYD2_LEAVE;
1036 static void
1037 _ncl_kbs(struct line *l)
1039 ssize_t c, t;
1040 NYD2_ENTER;
1042 c = l->cursor;
1043 t = l->topins;
1045 if (c > 0) {
1046 putchar('\b');
1047 l->cursor = --c;
1048 l->topins = --t;
1049 t -= c;
1050 _ncl_bs_eof_dvup(l->line.cells + c, t);
1051 } else
1052 putchar('\a');
1053 NYD2_LEAVE;
1056 static void
1057 _ncl_kkill(struct line *l, bool_t dobell)
1059 size_t j, c, i;
1060 NYD2_ENTER;
1062 c = l->cursor;
1063 i = (size_t)(l->topins - c);
1065 if (i > 0) {
1066 l->topins = c;
1067 for (j = i; j != 0; --j)
1068 putchar(' ');
1069 for (j = i; j != 0; --j)
1070 putchar('\b');
1071 } else if (dobell)
1072 putchar('\a');
1073 NYD2_LEAVE;
1076 static ssize_t
1077 _ncl_keof(struct line *l)
1079 size_t c, t;
1080 ssize_t i;
1081 NYD2_ENTER;
1083 c = l->cursor;
1084 t = l->topins;
1085 i = (ssize_t)(t - c);
1087 if (i > 0) {
1088 l->topins = --t;
1089 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1090 } else if (t == 0 /*&& !ok_blook(ignoreeof)*/) {
1091 /*fputs("^D", stdout);
1092 fflush(stdout);*/
1093 i = -1;
1094 /*} else {
1095 putchar('\a');
1096 i = 0;*/
1098 NYD2_LEAVE;
1099 return i;
1102 static void
1103 _ncl_kleft(struct line *l)
1105 NYD2_ENTER;
1106 if (l->cursor > 0) {
1107 --l->cursor;
1108 putchar('\b');
1109 } else
1110 putchar('\a');
1111 NYD2_LEAVE;
1114 static void
1115 _ncl_kright(struct line *l)
1117 NYD2_ENTER;
1118 if (l->cursor < l->topins) {
1119 ++l->cursor;
1120 fputs(l->nd, stdout);
1121 } else
1122 putchar('\a');
1123 NYD2_LEAVE;
1126 static void
1127 _ncl_krefresh(struct line *l)
1129 struct cell *cap;
1130 size_t i;
1131 NYD2_ENTER;
1133 putchar('\r');
1134 if (l->prompt != NULL && *l->prompt != '\0')
1135 fputs(l->prompt, stdout);
1136 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1137 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1138 for (i = l->topins - l->cursor; i > 0; --i)
1139 putchar('\b');
1140 NYD2_LEAVE;
1143 static void
1144 _ncl_kbwddelw(struct line *l)
1146 ssize_t i;
1147 size_t c, t, j;
1148 struct cell *cap;
1149 NYD2_ENTER;
1151 i = _ncl_wboundary(l, -1);
1152 if (i <= 0) {
1153 if (i < 0)
1154 putchar('\a');
1155 goto jleave;
1158 c = l->cursor - i;
1159 t = l->topins;
1160 l->topins = t - i;
1161 l->cursor = c;
1162 cap = l->line.cells + c;
1164 if (t != l->cursor) {
1165 j = t - c + i;
1166 memmove(cap, cap + i, j * sizeof(*cap));
1169 for (j = i; j > 0; --j)
1170 putchar('\b');
1171 for (j = l->topins - c; j > 0; ++cap, --j)
1172 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1173 for (j = i; j > 0; --j)
1174 putchar(' ');
1175 for (j = t - c; j > 0; --j)
1176 putchar('\b');
1177 jleave:
1178 NYD2_LEAVE;
1181 static void
1182 _ncl_kgow(struct line *l, ssize_t dir)
1184 ssize_t i;
1185 NYD2_ENTER;
1187 i = _ncl_wboundary(l, dir);
1188 if (i <= 0) {
1189 if (i < 0)
1190 putchar('\a');
1191 goto jleave;
1194 if (dir < 0) {
1195 l->cursor -= i;
1196 while (i-- > 0)
1197 putchar('\b');
1198 } else {
1199 l->cursor += i;
1200 while (i-- > 0)
1201 fputs(l->nd, stdout);
1203 jleave:
1204 NYD2_LEAVE;
1207 static void
1208 _ncl_kother(struct line *l, wchar_t wc)
1210 /* Append if at EOL, insert otherwise;
1211 * since we may move around character-wise, always use a fresh ps */
1212 mbstate_t ps;
1213 struct cell cell, *cap;
1214 size_t i, c;
1215 NYD2_ENTER;
1217 /* First init a cell and see wether we'll really handle this wc */
1218 cell.wc = wc;
1219 memset(&ps, 0, sizeof ps);
1220 i = wcrtomb(cell.cbuf, wc, &ps);
1221 if (i > MB_LEN_MAX)
1222 goto jleave;
1223 cell.count = (ui32_t)i;
1224 if (options & OPT_ENC_MBSTATE) {
1225 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1226 if (i == 1)
1228 else if (--i < MB_LEN_MAX)
1229 cell.count += (ui32_t)i;
1230 else
1231 goto jleave;
1234 /* Yes, we will! Place it in the array */
1235 c = l->cursor++;
1236 i = l->topins++ - c;
1237 cap = l->line.cells + c;
1238 if (i > 0)
1239 memmove(cap + 1, cap, i * sizeof(cell));
1240 memcpy(cap, &cell, sizeof cell);
1242 /* And update visual */
1243 c = i;
1245 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1246 while ((++cap, i-- != 0));
1247 while (c-- != 0)
1248 putchar('\b');
1249 jleave:
1250 NYD2_LEAVE;
1253 # ifdef HAVE_HISTORY
1254 static size_t
1255 __ncl_khist_shared(struct line *l, struct hist *hp)
1257 size_t rv;
1258 NYD2_ENTER;
1260 if ((l->hist = hp) != NULL) {
1261 l->defc.s = savestrbuf(hp->dat, hp->len);
1262 rv =
1263 l->defc.l = hp->len;
1264 if (l->topins > 0) {
1265 _ncl_khome(l, FAL0);
1266 _ncl_kkill(l, FAL0);
1268 } else {
1269 putchar('\a');
1270 rv = 0;
1272 NYD2_LEAVE;
1273 return rv;
1276 static size_t
1277 _ncl_khist(struct line *l, bool_t backwd)
1279 struct hist *hp;
1280 size_t rv;
1281 NYD2_ENTER;
1283 /* If we're not in history mode yet, save line content;
1284 * also, disallow forward search, then, and, of course, bail unless we
1285 * do have any history at all */
1286 if ((hp = l->hist) == NULL) {
1287 if (!backwd)
1288 goto jleave;
1289 if ((hp = _ncl_hist) == NULL)
1290 goto jleave;
1291 _ncl_cell2save(l);
1292 goto jleave;
1295 hp = backwd ? hp->older : hp->younger;
1296 jleave:
1297 rv = __ncl_khist_shared(l, hp);
1298 NYD2_LEAVE;
1299 return rv;
1302 static size_t
1303 _ncl_krhist(struct line *l)
1305 struct str orig_savec;
1306 struct hist *hp = NULL;
1307 size_t rv;
1308 NYD2_ENTER;
1310 /* We cannot complete an empty line */
1311 if (l->topins == 0) {
1312 /* XXX The upcoming hard reset would restore a set savec buffer,
1313 * XXX so forcefully reset that. A cleaner solution would be to
1314 * XXX reset it whenever a restore is no longer desired */
1315 l->savec.s = NULL, l->savec.l = 0;
1316 goto jleave;
1318 if ((hp = l->hist) == NULL) {
1319 if ((hp = _ncl_hist) == NULL)
1320 goto jleave;
1321 orig_savec.s = NULL;
1322 orig_savec.l = 0; /* silence CC */
1323 } else if ((hp = hp->older) == NULL)
1324 goto jleave;
1325 else
1326 orig_savec = l->savec;
1328 if (orig_savec.s == NULL)
1329 _ncl_cell2save(l);
1330 for (; hp != NULL; hp = hp->older)
1331 if (is_prefix(l->savec.s, hp->dat))
1332 break;
1333 if (orig_savec.s != NULL)
1334 l->savec = orig_savec;
1335 jleave:
1336 rv = __ncl_khist_shared(l, hp);
1337 NYD2_LEAVE;
1338 return rv;
1340 # endif
1342 # ifdef HAVE_TABEXPAND
1343 static size_t
1344 _ncl_kht(struct line *l)
1346 struct str orig, bot, topp, sub, exp;
1347 struct cell *cword, *ctop, *cx;
1348 bool_t set_savec = FAL0;
1349 size_t rv = 0;
1350 NYD2_ENTER;
1352 /* We cannot expand an empty line */
1353 if (l->topins == 0)
1354 goto jleave;
1356 /* Get plain line data; if this is the first expansion/xy, update the
1357 * very original content so that ^G gets the origin back */
1358 orig = l->savec;
1359 _ncl_cell2save(l);
1360 exp = l->savec;
1361 if (orig.s != NULL)
1362 l->savec = orig;
1363 else
1364 set_savec = TRU1;
1365 orig = exp;
1367 cword = l->line.cells;
1368 ctop = cword + l->cursor;
1370 /* topp: separate data right of cursor */
1371 if ((cx = cword + l->topins) != ctop) {
1372 for (rv = 0; cx > ctop; --cx)
1373 rv += cx->count;
1374 topp.l = rv;
1375 topp.s = orig.s + orig.l - rv;
1376 } else
1377 topp.s = NULL, topp.l = 0;
1379 /* bot, sub: we cannot expand the entire data left of cursor, but only
1380 * the last "word", so separate them */
1381 while (cx > cword && !iswspace(cx[-1].wc))
1382 --cx;
1383 for (rv = 0; cword < cx; ++cword)
1384 rv += cword->count;
1385 sub =
1386 bot = orig;
1387 bot.l = rv;
1388 sub.s += rv;
1389 sub.l -= rv;
1390 sub.l -= topp.l;
1392 /* Leave room for "implicit asterisk" expansion, as below */
1393 if (sub.l == 0) {
1394 sub.s = UNCONST("*");
1395 sub.l = 1;
1396 } else {
1397 exp.s = salloc(sub.l + 1 +1);
1398 memcpy(exp.s, sub.s, sub.l);
1399 exp.s[sub.l] = '\0';
1400 sub.s = exp.s;
1403 /* TODO there is a TODO note upon fexpand() with multi-return;
1404 * TODO if that will change, the if() below can be simplified */
1405 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1406 jredo:
1407 hold_all_sigs();
1408 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1409 rele_all_sigs();
1411 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1412 goto jnope;
1413 /* If the expansion equals the original string, assume the user wants what
1414 * is usually known as tab completion, append `*' and restart */
1415 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1416 if (sub.s[sub.l - 1] == '*')
1417 goto jnope;
1418 sub.s[sub.l++] = '*';
1419 sub.s[sub.l] = '\0';
1420 goto jredo;
1423 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1424 * Take care to take *prompt* into account, since we don't know
1425 * anything about it's visual length (fputs(3) is used), simply
1426 * assume each character requires two columns */
1427 /* TODO the problem is that we loose control otherwise; in the best
1428 * TODO case the user can control via ^A and ^K etc., but be safe;
1429 * TODO we cannot simply adjust fexpand() because we don't know how
1430 * TODO that is implemented... The real solution would be to check
1431 * TODO wether we fit on a line, and start a pager if not.
1432 * TODO However, that should be part of a real tab-COMPLETION, then,
1433 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1434 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1435 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1436 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1437 exp.s = UNCONST("[ERR_TOO_LONG]");
1438 exp.l = sizeof("[ERR_TOO_LONG]") - 1;
1439 topp.l = 0;
1440 if (rv + bot.l + exp.l >= MAX_INPUT)
1441 bot.l = 0;
1442 if (rv + exp.l >= MAX_INPUT) {
1443 exp.s = UNCONST("[ERR]");
1444 exp.l = sizeof("[ERR]") - 1;
1448 orig.l = bot.l + exp.l + topp.l;
1449 orig.s = salloc(orig.l + 5 +1);
1450 if ((rv = bot.l) > 0)
1451 memcpy(orig.s, bot.s, rv);
1452 memcpy(orig.s + rv, exp.s, exp.l);
1453 rv += exp.l;
1454 if (topp.l > 0) {
1455 memcpy(orig.s + rv, topp.s, topp.l);
1456 rv += topp.l;
1458 orig.s[rv] = '\0';
1460 l->defc = orig;
1461 _ncl_khome(l, FAL0);
1462 _ncl_kkill(l, FAL0);
1463 jleave:
1464 NYD2_LEAVE;
1465 return rv;
1466 jnope:
1467 /* If we've provided a default content, but failed to expand, there is
1468 * nothing we can "revert to": drop that default again */
1469 if (set_savec)
1470 l->savec.s = NULL, l->savec.l = 0;
1471 rv = 0;
1472 goto jleave;
1474 # endif /* HAVE_TABEXPAND */
1476 static ssize_t
1477 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1478 SMALLOC_DEBUG_ARGS)
1480 /* We want to save code, yet we may have to incorporate a lines'
1481 * default content and / or default input to switch back to after some
1482 * history movement; let "len > 0" mean "have to display some data
1483 * buffer", and only otherwise read(2) it */
1484 mbstate_t ps[2];
1485 struct line l;
1486 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp, cursor_maybe, cursor_store;
1487 wchar_t wc;
1488 ssize_t rv;
1489 NYD_ENTER;
1491 memset(&l, 0, sizeof l);
1492 l.line.cbuf = *buf;
1493 if (len != 0) {
1494 l.defc.s = savestrbuf(*buf, len);
1495 l.defc.l = len;
1497 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1498 l.prompt = prompt = "?ERR?";
1499 /* TODO *l.nd=='\0' : instead adjust accmacvar.c to disallow empty vals */
1500 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1501 l.nd = "\033[C"; /* XXX no "magic" constant */
1502 l.x_buf = buf;
1503 l.x_bufsize = bufsize;
1505 if (prompt != NULL && *prompt != '\0')
1506 fputs(prompt, stdout);
1507 fflush(stdout);
1509 jrestart:
1510 memset(ps, 0, sizeof ps);
1511 cursor_maybe = cursor_store = 0;
1512 /* TODO: NCL: we should output the reset sequence when we jrestart:
1513 * TODO: NCL: if we are using a stateful encoding? !
1514 * TODO: NCL: in short: this is not yet well understood */
1515 for (;;) {
1516 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1518 /* Normal read(2)? Else buffer-takeover: speed this one up */
1519 if (len == 0)
1520 cbufp =
1521 cbuf = cbuf_base;
1522 else {
1523 assert(l.defc.l > 0 && l.defc.s != NULL);
1524 cbufp =
1525 cbuf = l.defc.s + (l.defc.l - len);
1526 cbufp += len;
1529 /* Read in the next complete multibyte character */
1530 for (;;) {
1531 if (len == 0) {
1532 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1533 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1534 continue;
1535 goto jleave;
1537 ++cbufp;
1540 /* Ach! the ISO C multibyte handling!
1541 * Encodings with locking shift states cannot really be helped, since
1542 * it is impossible to only query the shift state, as opposed to the
1543 * entire shift state + character pair (via ISO C functions) */
1544 rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), ps + 0);
1545 if (rv <= 0) {
1546 /* Any error during take-over can only result in a hard reset;
1547 * Otherwise, if it's a hard error, or if too many redundant shift
1548 * sequences overflow our buffer, also perform a hard reset */
1549 if (len != 0 || rv == -1 ||
1550 sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)) {
1551 l.savec.s = l.defc.s = NULL,
1552 l.savec.l = l.defc.l = len = 0;
1553 putchar('\a');
1554 wc = 'G';
1555 goto jreset;
1557 /* Otherwise, due to the way we deal with the buffer, we need to
1558 * restore the mbstate_t from before this conversion */
1559 ps[0] = ps[1];
1560 continue;
1563 if (len != 0 && (len -= (size_t)rv) == 0)
1564 l.defc.s = NULL, l.defc.l = 0;
1565 ps[1] = ps[0];
1566 break;
1569 /* Don't interpret control bytes during buffer take-over */
1570 if (cbuf != cbuf_base)
1571 goto jprint;
1572 switch (wc) {
1573 case 'A' ^ 0x40: /* cursor home */
1574 _ncl_khome(&l, TRU1);
1575 break;
1576 case 'B' ^ 0x40: /* backward character */
1577 j_b:
1578 _ncl_kleft(&l);
1579 break;
1580 /* 'C': interrupt (CTRL-C) */
1581 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1582 if ((rv = _ncl_keof(&l)) < 0)
1583 goto jleave;
1584 break;
1585 case 'E' ^ 0x40: /* end of line */
1586 _ncl_kend(&l);
1587 break;
1588 case 'F' ^ 0x40: /* forward character */
1589 j_f:
1590 _ncl_kright(&l);
1591 break;
1592 /* 'G' below */
1593 case 'H' ^ 0x40: /* backspace */
1594 case '\177':
1595 _ncl_kbs(&l);
1596 break;
1597 case 'I' ^ 0x40: /* horizontal tab */
1598 # ifdef HAVE_TABEXPAND
1599 if ((len = _ncl_kht(&l)) > 0)
1600 goto jrestart;
1601 # endif
1602 goto jbell;
1603 case 'J' ^ 0x40: /* NL (\n) */
1604 goto jdone;
1605 case 'G' ^ 0x40: /* full reset */
1606 jreset:
1607 /* FALLTHRU */
1608 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1609 _ncl_khome(&l, FAL0);
1610 /* FALLTHRU */
1611 case 'K' ^ 0x40: /* kill from cursor to end of line */
1612 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1613 /* (Handle full reset?) */
1614 if (wc == ('G' ^ 0x40)) {
1615 # ifdef HAVE_HISTORY
1616 l.hist = NULL;
1617 # endif
1618 if ((len = l.savec.l) != 0) {
1619 l.defc = l.savec;
1620 l.savec.s = NULL, l.savec.l = 0;
1621 } else
1622 len = l.defc.l;
1624 fflush(stdout);
1625 goto jrestart;
1626 case 'L' ^ 0x40: /* repaint line */
1627 j_l:
1628 _ncl_krefresh(&l);
1629 break;
1630 /* 'M': CR (\r) */
1631 case 'N' ^ 0x40: /* history next */
1632 j_n:
1633 # ifdef HAVE_HISTORY
1634 if (l.hist == NULL)
1635 goto jbell;
1636 if ((len = _ncl_khist(&l, FAL0)) > 0)
1637 goto jrestart;
1638 wc = 'G' ^ 0x40;
1639 goto jreset;
1640 # else
1641 goto jbell;
1642 # endif
1643 /* 'O' */
1644 case 'O' ^ 0x40: /* `dp' */
1645 putchar('\n');
1646 cbuf_base[0] = 'd';
1647 cbuf_base[1] = 'p';
1648 cbuf_base[2] = '\0';
1649 pstate &= ~PS_HOOK_MASK;
1650 execute(cbuf_base, 2);
1651 goto j_l;
1652 case 'P' ^ 0x40: /* history previous */
1653 j_p:
1654 # ifdef HAVE_HISTORY
1655 if ((len = _ncl_khist(&l, TRU1)) > 0)
1656 goto jrestart;
1657 wc = 'G' ^ 0x40;
1658 goto jreset;
1659 # else
1660 goto jbell;
1661 # endif
1662 /* 'Q': no code */
1663 case 'R' ^ 0x40: /* reverse history search */
1664 # ifdef HAVE_HISTORY
1665 if ((len = _ncl_krhist(&l)) > 0)
1666 goto jrestart;
1667 wc = 'G' ^ 0x40;
1668 goto jreset;
1669 # else
1670 goto jbell;
1671 # endif
1672 /* 'S': no code */
1673 /* 'U' above */
1674 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1675 case 'W' ^ 0x40: /* backward delete "word" */
1676 _ncl_kbwddelw(&l);
1677 break;
1678 case 'X' ^ 0x40: /* move cursor forward "word" */
1679 _ncl_kgow(&l, +1);
1680 break;
1681 case 'Y' ^ 0x40: /* move cursor backward "word" */
1682 _ncl_kgow(&l, -1);
1683 break;
1684 /* 'Z': suspend (CTRL-Z) */
1685 case 0x1B:
1686 if (cursor_maybe++ != 0)
1687 goto jreset;
1688 continue;
1689 default:
1690 /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */
1691 if (cursor_maybe > 0) {
1692 if (++cursor_maybe == 2) {
1693 if (wc == L'[')
1694 continue;
1695 cursor_maybe = 0;
1696 } else if (cursor_maybe == 3) {
1697 cursor_maybe = 0;
1698 switch (wc) {
1699 default: break;
1700 case L'A': goto j_p;
1701 case L'B': goto j_n;
1702 case L'C': goto j_f;
1703 case L'D': goto j_b;
1704 case L'H':
1705 cursor_store = '0';
1706 goto J_xterm_noapp;
1707 case L'F':
1708 cursor_store = '$';
1709 goto J_xterm_noapp;
1710 case L'1':
1711 case L'4':
1712 case L'5':
1713 case L'6':
1714 cursor_store = ((wc == L'1') ? '0' :
1715 (wc == L'4' ? '$' : (wc == L'5' ? '-' : '+')));
1716 cursor_maybe = 3;
1717 continue;
1719 _ncl_kother(&l, L'[');
1720 } else {
1721 cursor_maybe = 0;
1722 if (wc == L'~')
1723 J_xterm_noapp: {
1724 char x[2];
1725 x[0] = cursor_store;
1726 x[1] = '\0';
1727 putchar('\n');
1728 c_scroll(x);
1729 cursor_store = 0;
1730 goto j_l;
1732 _ncl_kother(&l, L'[');
1733 _ncl_kother(&l, (wchar_t)cursor_store);
1734 cursor_store = 0;
1737 jprint:
1738 if (iswprint(wc)) {
1739 _ncl_kother(&l, wc);
1740 /* Don't clear the history during takeover..
1741 * ..and also avoid fflush()ing unless we've worked entire buffer */
1742 if (len > 0)
1743 continue;
1744 # ifdef HAVE_HISTORY
1745 if (cbuf == cbuf_base)
1746 l.hist = NULL;
1747 # endif
1748 } else {
1749 jbell:
1750 putchar('\a');
1752 break;
1754 fflush(stdout);
1757 /* We have a completed input line, convert the struct cell data to its
1758 * plain character equivalent */
1759 jdone:
1760 putchar('\n');
1761 fflush(stdout);
1762 len = _ncl_cell2dat(&l);
1763 rv = (ssize_t)len;
1764 jleave:
1765 NYD_LEAVE;
1766 return rv;
1769 FL void
1770 tty_init(void)
1772 # ifdef HAVE_HISTORY
1773 long hs;
1774 char *v, *lbuf;
1775 FILE *f;
1776 size_t lsize, cnt, llen;
1777 # endif
1778 NYD_ENTER;
1780 # ifdef HAVE_HISTORY
1781 _CL_HISTSIZE(hs);
1782 _ncl_hist_size = 0;
1783 _ncl_hist_size_max = hs;
1784 if (hs == 0)
1785 goto jleave;
1787 _CL_HISTFILE(v);
1788 if (v == NULL)
1789 goto jleave;
1791 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1792 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1793 if (f == NULL)
1794 goto jdone;
1795 (void)file_lock(fileno(f), FLT_READ, 0,0, 500);
1797 lbuf = NULL;
1798 lsize = 0;
1799 cnt = fsize(f);
1800 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1801 if (llen > 0 && lbuf[llen - 1] == '\n')
1802 lbuf[--llen] = '\0';
1803 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1804 continue;
1805 else {
1806 bool_t isgabby = (lbuf[0] == '*');
1807 _ncl_hist_load = TRU1;
1808 tty_addhist(lbuf + isgabby, isgabby);
1809 _ncl_hist_load = FAL0;
1812 if (lbuf != NULL)
1813 free(lbuf);
1815 fclose(f);
1816 jdone:
1817 rele_all_sigs(); /* XXX remove jumps */
1818 jleave:
1819 # endif /* HAVE_HISTORY */
1820 NYD_LEAVE;
1823 FL void
1824 tty_destroy(void)
1826 # ifdef HAVE_HISTORY
1827 long hs;
1828 char *v;
1829 struct hist *hp;
1830 bool_t dogabby;
1831 FILE *f;
1832 # endif
1833 NYD_ENTER;
1835 # ifdef HAVE_HISTORY
1836 _CL_HISTSIZE(hs);
1837 if (hs == 0)
1838 goto jleave;
1839 _CL_HISTFILE(v);
1840 if (v == NULL)
1841 goto jleave;
1843 dogabby = ok_blook(history_gabby_persist);
1845 if ((hp = _ncl_hist) != NULL)
1846 for (; hp->older != NULL; hp = hp->older)
1847 if ((dogabby || !hp->isgabby) && --hs == 0)
1848 break;
1850 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1851 f = fopen(v, "w"); /* TODO temporary + rename?! */
1852 if (f == NULL)
1853 goto jdone;
1854 (void)file_lock(fileno(f), FLT_WRITE, 0,0, 500);
1855 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1856 goto jclose;
1858 for (; hp != NULL; hp = hp->younger) {
1859 if (!hp->isgabby || dogabby) {
1860 if (hp->isgabby)
1861 putc('*', f);
1862 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1863 putc('\n', f);
1866 jclose:
1867 fclose(f);
1868 jdone:
1869 rele_all_sigs(); /* XXX remove jumps */
1870 jleave:
1871 # endif /* HAVE_HISTORY */
1872 NYD_LEAVE;
1875 FL void
1876 tty_signal(int sig)
1878 sigset_t nset, oset;
1879 NYD_X; /* Signal handler */
1881 switch (sig) {
1882 case SIGWINCH:
1883 /* We don't deal with SIGWINCH, yet get called from main.c */
1884 break;
1885 default:
1886 _ncl_term_mode(FAL0);
1887 _ncl_sigs_down();
1888 sigemptyset(&nset);
1889 sigaddset(&nset, sig);
1890 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1891 n_raise(sig);
1892 /* When we come here we'll continue editing, so reestablish */
1893 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1894 _ncl_sigs_up();
1895 _ncl_term_mode(TRU1);
1896 break;
1900 FL int
1901 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1902 SMALLOC_DEBUG_ARGS)
1904 ssize_t nn;
1905 NYD_ENTER;
1907 /* Of course we have races here, but they cannot be avoided on POSIX
1908 * (except by even *more* actions) */
1909 _ncl_sigs_up();
1910 _ncl_term_mode(TRU1);
1911 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1912 _ncl_term_mode(FAL0);
1913 _ncl_sigs_down();
1914 NYD_LEAVE;
1915 return (int)nn;
1918 FL void
1919 tty_addhist(char const *s, bool_t isgabby)
1921 # ifdef HAVE_HISTORY
1922 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1923 ui32_t l;
1924 struct hist *h, *o, *y;
1925 # endif
1926 NYD_ENTER;
1927 UNUSED(s);
1928 UNUSED(isgabby);
1930 # ifdef HAVE_HISTORY
1931 if (isgabby && !ok_blook(history_gabby))
1932 goto j_leave;
1933 if (_ncl_hist_size_max == 0)
1934 goto j_leave;
1935 _CL_CHECK_ADDHIST(s, goto j_leave);
1937 l = (ui32_t)strlen(s);
1939 /* Eliminating duplicates is expensive, but simply inacceptable so
1940 * during the load of a potentially large history file! */
1941 if (!_ncl_hist_load)
1942 for (h = _ncl_hist; h != NULL; h = h->older)
1943 if (h->len == l && !strcmp(h->dat, s)) {
1944 hold_all_sigs(); /* TODO */
1945 if (h->isgabby)
1946 h->isgabby = !!isgabby;
1947 o = h->older;
1948 y = h->younger;
1949 if (o != NULL)
1950 o->younger = y;
1951 else
1952 _ncl_hist_tail = y;
1953 if (y != NULL)
1954 y->older = o;
1955 else
1956 _ncl_hist = o;
1957 goto jleave;
1959 hold_all_sigs();
1961 ++_ncl_hist_size;
1962 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1963 --_ncl_hist_size;
1964 if ((h = _ncl_hist_tail) != NULL) {
1965 if ((_ncl_hist_tail = h->younger) == NULL)
1966 _ncl_hist = NULL;
1967 else
1968 _ncl_hist_tail->older = NULL;
1969 free(h);
1973 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1974 h->isgabby = !!isgabby;
1975 h->len = l;
1976 memcpy(h->dat, s, l +1);
1977 jleave:
1978 if ((h->older = _ncl_hist) != NULL)
1979 _ncl_hist->younger = h;
1980 else
1981 _ncl_hist_tail = h;
1982 h->younger = NULL;
1983 _ncl_hist = h;
1985 rele_all_sigs();
1986 j_leave:
1987 # endif
1988 NYD_LEAVE;
1991 # ifdef HAVE_HISTORY
1992 FL int
1993 c_history(void *v)
1995 C_HISTORY_SHARED;
1997 jlist: {
1998 FILE *fp;
1999 size_t i, b;
2000 struct hist *h;
2002 if (_ncl_hist == NULL)
2003 goto jleave;
2005 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
2006 n_perr(_("tmpfile"), 0);
2007 v = NULL;
2008 goto jleave;
2011 i = _ncl_hist_size;
2012 b = 0;
2013 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
2014 fprintf(fp,
2015 "%c%4" PRIuZ ". %-50.50s (%4" PRIuZ "+%2" PRIu32 " bytes)\n",
2016 (h->isgabby ? '*' : ' '), i, h->dat, b, h->len);
2018 page_or_print(fp, i);
2019 Fclose(fp);
2021 goto jleave;
2023 jclear: {
2024 struct hist *h;
2026 while ((h = _ncl_hist) != NULL) {
2027 _ncl_hist = h->older;
2028 free(h);
2030 _ncl_hist_tail = NULL;
2031 _ncl_hist_size = 0;
2033 goto jleave;
2035 jentry: {
2036 struct hist *h;
2038 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2039 entry = (long)_ncl_hist_size - entry;
2040 for (h = _ncl_hist;; h = h->older)
2041 if (h == NULL)
2042 break;
2043 else if (entry-- != 0)
2044 continue;
2045 else {
2046 v = temporary_arg_v_store = h->dat;
2047 goto jleave;
2050 v = NULL;
2052 goto jleave;
2054 # endif /* HAVE_HISTORY */
2055 #endif /* HAVE_NCL */
2058 * The really-nothing-at-all implementation
2061 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2062 FL void
2063 tty_init(void)
2065 NYD_ENTER;
2066 NYD_LEAVE;
2069 FL void
2070 tty_destroy(void)
2072 NYD_ENTER;
2073 NYD_LEAVE;
2076 FL void
2077 tty_signal(int sig)
2079 NYD_X; /* Signal handler */
2080 UNUSED(sig);
2083 FL int
2084 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2085 SMALLOC_DEBUG_ARGS)
2087 int rv;
2088 NYD_ENTER;
2090 if (prompt != NULL) {
2091 if (*prompt != '\0')
2092 fputs(prompt, stdout);
2093 fflush(stdout);
2095 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2096 NYD_LEAVE;
2097 return rv;
2100 FL void
2101 tty_addhist(char const *s, bool_t isgabby)
2103 NYD_ENTER;
2104 UNUSED(s);
2105 UNUSED(isgabby);
2106 NYD_LEAVE;
2108 #endif /* nothing at all */
2110 /* s-it-mode */