Review: imap_search.c
[s-mailx.git] / tty.c
blob58128386cc0773ad98124a0ec2659e0e0c3fa0f1
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ TTY interaction.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /* The NCL version is
9 * Copyright (c) 2013 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
11 * Permission to use, copy, modify, and/or distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 * Copyright (c) 1980, 1993
25 * The Regents of the University of California. All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 * notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 * notice, this list of conditions and the following disclaimer in the
34 * documentation and/or other materials provided with the distribution.
35 * 3. All advertising materials mentioning features or use of this software
36 * must display the following acknowledgement:
37 * This product includes software developed by the University of
38 * California, Berkeley and its contributors.
39 * 4. Neither the name of the University nor the names of its contributors
40 * may be used to endorse or promote products derived from this software
41 * without specific prior written permission.
43 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
44 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
45 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
46 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
47 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
48 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
49 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
50 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
51 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
52 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
53 * SUCH DAMAGE.
56 #ifndef HAVE_AMALGAMATION
57 # include "nail.h"
58 #endif
60 #ifdef HAVE_READLINE
61 # include <readline/readline.h>
62 # ifdef HAVE_HISTORY
63 # include <readline/history.h>
64 # endif
65 #elif defined HAVE_EDITLINE
66 # include <histedit.h>
67 #endif
69 /* Shared history support macros */
70 #ifdef HAVE_HISTORY
71 # define _CL_HISTFILE(S) \
72 do {\
73 S = ok_vlook(NAIL_HISTFILE);\
74 if ((S) != NULL)\
75 S = fexpand(S, FEXP_LOCAL);\
76 } while (0)
78 # define _CL_HISTSIZE(V) \
79 do {\
80 char const *__sv = ok_vlook(NAIL_HISTSIZE);\
81 long __rv;\
82 if (__sv == NULL || *__sv == '\0' || (__rv = strtol(__sv, NULL, 10)) == 0)\
83 (V) = HIST_SIZE;\
84 else if (__rv < 0)\
85 (V) = 0;\
86 else\
87 (V) = __rv;\
88 } while (0)
90 # define _CL_CHECK_ADDHIST(S,NOACT) \
91 do {\
92 switch (*(S)) {\
93 case '\0':\
94 case ' ':\
95 NOACT;\
96 default:\
97 break;\
99 } while (0)
101 # define C_HISTORY_SHARED \
102 char **argv = v;\
103 long entry;\
104 NYD_ENTER;\
106 if (*argv == NULL)\
107 goto jlist;\
108 if (argv[1] != NULL)\
109 goto jerr;\
110 if (!asccasecmp(*argv, "show"))\
111 goto jlist;\
112 if (!asccasecmp(*argv, "clear"))\
113 goto jclear;\
114 if ((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
115 goto jentry;\
116 jerr:\
117 fprintf(stderr, "Synopsis: history: %s\n", tr(431,\
118 "<show> (default), <clear> or select <NO> from editor history"));\
119 v = NULL;\
120 jleave:\
121 NYD_LEAVE;\
122 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
123 #endif /* HAVE_HISTORY */
125 /* fexpand() flags for expand-on-tab */
126 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
129 * Because we have multiple identical implementations, change file layout a bit
130 * and place the implementations one after the other below the other externals
133 FL bool_t
134 getapproval(char const *prompt, bool_t noninteract_default)
136 bool_t rv;
137 NYD_ENTER;
139 if (!(options & OPT_INTERACTIVE)) {
140 rv = noninteract_default;
141 goto jleave;
144 if (prompt == NULL)
145 prompt = tr(264, "Continue (y/n)? ");
147 rv = FAL0;
148 if (readline_input(prompt, FAL0, &termios_state.ts_linebuf,
149 &termios_state.ts_linesize, NULL) >= 0)
150 switch (termios_state.ts_linebuf[0]) {
151 case 'y':
152 case 'Y':
153 rv = TRU1;
154 /* FALLTHRU */
155 default:
156 break;
158 termios_state_reset();
159 jleave:
160 NYD_LEAVE;
161 return rv;
164 FL bool_t
165 yorn(char const *msg) /* TODO obsolete */
167 bool_t rv;
168 NYD_ENTER;
170 rv = getapproval(msg, TRU1);
171 NYD_LEAVE;
172 return rv;
175 FL char *
176 getuser(char const *query)
178 char *user = NULL;
179 NYD_ENTER;
181 if (query == NULL)
182 query = tr(509, "User: ");
184 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
185 &termios_state.ts_linesize, NULL) >= 0)
186 user = termios_state.ts_linebuf;
187 termios_state_reset();
188 NYD_LEAVE;
189 return user;
192 FL char *
193 getpassword(char const *query) /* FIXME encaps ttystate signal safe */
195 struct termios tios;
196 char *pass = NULL;
197 NYD_ENTER;
199 if (query == NULL)
200 query = tr(510, "Password: ");
201 fputs(query, stdout);
202 fflush(stdout);
204 /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
205 * FIXME foreground pgrp, and can fail with EINTR!! */
206 if (options & OPT_TTYIN) {
207 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
208 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
209 termios_state.ts_needs_reset = TRU1;
210 tios.c_iflag &= ~(ISTRIP);
211 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
212 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
215 if (readline_restart(stdin, &termios_state.ts_linebuf,
216 &termios_state.ts_linesize, 0) >= 0)
217 pass = termios_state.ts_linebuf;
218 termios_state_reset();
220 if (options & OPT_TTYIN)
221 fputc('\n', stdout);
222 NYD_LEAVE;
223 return pass;
226 FL bool_t
227 getcredentials(char **user, char **pass)
229 bool_t rv;
230 char *u, *p;
231 NYD_ENTER;
233 rv = TRU1;
234 u = *user;
235 p = *pass;
237 if (u == NULL) {
238 if ((u = getuser(NULL)) == NULL)
239 rv = FAL0;
240 else if (p == NULL)
241 u = savestr(u);
242 *user = u;
245 if (p == NULL) {
246 if ((p = getpassword(NULL)) == NULL)
247 rv = FAL0;
248 *pass = p;
250 NYD_LEAVE;
251 return rv;
255 * readline(3)
258 #ifdef HAVE_READLINE
259 static sighandler_type _rl_shup;
260 static char * _rl_buf; /* pre_input() hook: initial line */
261 static int _rl_buflen; /* content, and its length */
263 static int _rl_pre_input(void);
265 static int
266 _rl_pre_input(void)
268 NYD_ENTER;
269 /* Handle leftover data from \ escaped former line */
270 rl_extend_line_buffer(_rl_buflen + 10);
271 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
272 rl_point = rl_end = _rl_buflen;
273 rl_pre_input_hook = (rl_hook_func_t*)NULL;
274 rl_redisplay();
275 NYD_LEAVE;
276 return 0;
279 FL void
280 tty_init(void)
282 # ifdef HAVE_HISTORY
283 long hs;
284 char *v;
285 # endif
286 NYD_ENTER;
288 rl_readline_name = UNCONST(uagent);
289 # ifdef HAVE_HISTORY
290 _CL_HISTSIZE(hs);
291 using_history();
292 stifle_history((int)hs);
293 # endif
294 rl_read_init_file(NULL);
296 /* Because rl_read_init_file() may have introduced yet a different
297 * history size limit, simply load and incorporate the history, leave
298 * it up to readline(3) to do the rest */
299 # ifdef HAVE_HISTORY
300 _CL_HISTFILE(v);
301 if (v != NULL)
302 read_history(v);
303 # endif
304 NYD_LEAVE;
307 FL void
308 tty_destroy(void)
310 # ifdef HAVE_HISTORY
311 char *v;
312 # endif
313 NYD_ENTER;
315 # ifdef HAVE_HISTORY
316 _CL_HISTFILE(v);
317 if (v != NULL)
318 write_history(v);
319 # endif
320 NYD_LEAVE;
323 FL void
324 tty_signal(int sig)
326 sigset_t nset, oset;
327 NYD_X; /* Signal handler */
329 switch (sig) {
330 # ifdef SIGWINCH
331 case SIGWINCH:
332 break;
333 # endif
334 case SIGHUP:
335 /* readline(3) doesn't catch it :( */
336 rl_free_line_state();
337 rl_cleanup_after_signal();
338 safe_signal(SIGHUP, _rl_shup);
339 sigemptyset(&nset);
340 sigaddset(&nset, sig);
341 sigprocmask(SIG_UNBLOCK, &nset, &oset);
342 kill(0, sig);
343 /* XXX When we come here we'll continue editing, so reestablish
344 * XXX cannot happen */
345 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
346 _rl_shup = safe_signal(SIGHUP, &tty_signal);
347 rl_reset_after_signal();
348 break;
349 default:
350 break;
354 FL int
355 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
356 SMALLOC_DEBUG_ARGS)
358 int nn;
359 char *line;
360 NYD_ENTER;
362 if (n > 0) {
363 _rl_buf = *linebuf;
364 _rl_buflen = (int)n;
365 rl_pre_input_hook = &_rl_pre_input;
368 _rl_shup = safe_signal(SIGHUP, &tty_signal);
369 line = readline(prompt != NULL ? prompt : "");
370 safe_signal(SIGHUP, _rl_shup);
372 if (line == NULL) {
373 nn = -1;
374 goto jleave;
376 n = strlen(line);
378 if (n >= *linesize) {
379 *linesize = LINESIZE + n + 1;
380 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
382 memcpy(*linebuf, line, n);
383 (free)(line);
384 (*linebuf)[n] = '\0';
385 nn = (int)n;
386 jleave:
387 NYD_LEAVE;
388 return nn;
391 FL void
392 tty_addhist(char const *s)
394 NYD_ENTER;
395 UNUSED(s);
396 # ifdef HAVE_HISTORY
397 _CL_CHECK_ADDHIST(s, goto jleave);
398 hold_all_sigs(); /* XXX too heavy */
399 add_history(s); /* XXX yet we jump away! */
400 rele_all_sigs(); /* XXX remove jumps */
401 jleave:
402 # endif
403 NYD_LEAVE;
406 # ifdef HAVE_HISTORY
407 FL int
408 c_history(void *v)
410 C_HISTORY_SHARED;
412 jlist: {
413 FILE *fp;
414 HISTORY_STATE *hs;
415 HIST_ENTRY **hl;
416 ul_it i, b;
418 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
419 NULL) {
420 perror("tmpfile");
421 v = NULL;
422 goto jleave;
425 hs = history_get_history_state();
427 for (i = (ul_it)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
428 char *cp = (*--hl)->line;
429 size_t sl = strlen(cp);
430 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n", i, cp, b, sl);
431 b += sl;
434 page_or_print(fp, (size_t)hs->length);
435 Fclose(fp);
437 goto jleave;
439 jclear:
440 clear_history();
441 goto jleave;
443 jentry: {
444 HISTORY_STATE *hs = history_get_history_state();
446 if (UICMP(z, entry, <=, hs->length))
447 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
448 else
449 v = NULL;
451 goto jleave;
453 # endif /* HAVE_HISTORY */
454 #endif /* HAVE_READLINE */
457 * BSD editline(3)
460 #ifdef HAVE_EDITLINE
461 static EditLine * _el_el; /* editline(3) handle */
462 static char const * _el_prompt; /* Current prompt */
463 # ifdef HAVE_HISTORY
464 static History * _el_hcom; /* History handle for commline */
465 # endif
467 static char const * _el_getprompt(void);
469 static char const *
470 _el_getprompt(void)
472 return _el_prompt;
475 FL void
476 tty_init(void)
478 # ifdef HAVE_HISTORY
479 HistEvent he;
480 long hs;
481 char *v;
482 # endif
483 NYD_ENTER;
485 # ifdef HAVE_HISTORY
486 _CL_HISTSIZE(hs);
487 _el_hcom = history_init();
488 history(_el_hcom, &he, H_SETSIZE, (int)hs);
489 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
490 # endif
492 _el_el = el_init(uagent, stdin, stdout, stderr);
493 el_set(_el_el, EL_SIGNAL, 1);
494 el_set(_el_el, EL_TERMINAL, NULL);
495 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
496 # ifdef HAVE_HISTORY
497 el_set(_el_el, EL_HIST, &history, _el_hcom);
498 # endif
499 el_set(_el_el, EL_EDITOR, "emacs");
500 # ifdef EL_PROMPT_ESC
501 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
502 # else
503 el_set(_el_el, EL_PROMPT, &_el_getprompt);
504 # endif
505 # if 0
506 el_set(_el_el, EL_ADDFN, "tab_complete",
507 "editline(3) internal completion function", &_el_file_cpl);
508 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
509 # endif
510 # ifdef HAVE_HISTORY
511 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
512 # endif
513 el_source(_el_el, NULL); /* Source ~/.editrc */
515 /* Because el_source() may have introduced yet a different history size
516 * limit, simply load and incorporate the history, leave it up to
517 * editline(3) to do the rest */
518 # ifdef HAVE_HISTORY
519 _CL_HISTFILE(v);
520 if (v != NULL)
521 history(_el_hcom, &he, H_LOAD, v);
522 # endif
523 NYD_LEAVE;
526 FL void
527 tty_destroy(void)
529 # ifdef HAVE_HISTORY
530 HistEvent he;
531 char *v;
532 # endif
533 NYD_ENTER;
535 el_end(_el_el);
537 # ifdef HAVE_HISTORY
538 _CL_HISTFILE(v);
539 if (v != NULL)
540 history(_el_hcom, &he, H_SAVE, v);
541 history_end(_el_hcom);
542 # endif
543 NYD_LEAVE;
546 FL void
547 tty_signal(int sig)
549 NYD_X; /* Signal handler */
550 switch (sig) {
551 # ifdef SIGWINCH
552 case SIGWINCH:
553 el_resize(_el_el);
554 break;
555 # endif
556 default:
557 break;
561 FL int
562 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
563 SMALLOC_DEBUG_ARGS)
565 int nn;
566 char const *line;
567 NYD_ENTER;
569 _el_prompt = (prompt != NULL) ? prompt : "";
570 if (n > 0)
571 el_push(_el_el, *linebuf);
572 line = el_gets(_el_el, &nn);
574 if (line == NULL) {
575 nn = -1;
576 goto jleave;
578 assert(nn >= 0);
579 n = (size_t)nn;
580 if (n > 0 && line[n - 1] == '\n')
581 nn = (int)--n;
583 if (n >= *linesize) {
584 *linesize = LINESIZE + n + 1;
585 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
587 memcpy(*linebuf, line, n);
588 (*linebuf)[n] = '\0';
589 jleave:
590 NYD_LEAVE;
591 return nn;
594 FL void
595 tty_addhist(char const *s)
597 # ifdef HAVE_HISTORY
598 /* Enlarge meaning of unique .. to something that rocks;
599 * xxx unfortunately this is expensive to do with editline(3)
600 * xxx maybe it would be better to hook the ptfs instead? */
601 HistEvent he;
602 int i;
603 # endif
604 NYD_ENTER;
605 UNUSED(s);
607 # ifdef HAVE_HISTORY
608 _CL_CHECK_ADDHIST(s, goto jleave);
610 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
611 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
612 i = history(_el_hcom, &he, H_NEXT))
613 if (!strcmp(he.str, s)) {
614 history(_el_hcom, &he, H_DEL, he.num);
615 break;
617 history(_el_hcom, &he, H_ENTER, s);
618 rele_all_sigs(); /* XXX remove jumps */
619 jleave:
620 # endif
621 NYD_LEAVE;
624 # ifdef HAVE_HISTORY
625 FL int
626 c_history(void *v)
628 C_HISTORY_SHARED;
630 jlist: {
631 HistEvent he;
632 FILE *fp;
633 size_t i, b;
634 int x;
636 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
637 NULL) {
638 perror("tmpfile");
639 v = NULL;
640 goto jleave;
643 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
644 b = 0;
645 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
646 x = history(_el_hcom, &he, H_NEXT)) {
647 size_t sl = strlen(he.str);
648 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
649 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
650 --i;
651 b += sl;
654 page_or_print(fp, i);
655 Fclose(fp);
657 goto jleave;
659 jclear: {
660 HistEvent he;
661 history(_el_hcom, &he, H_CLEAR);
663 goto jleave;
665 jentry: {
666 HistEvent he;
667 size_t i;
668 int x;
670 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
671 if (UICMP(z, entry, <=, i)) {
672 entry = (long)i - entry;
673 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
674 x = history(_el_hcom, &he, H_NEXT))
675 if (entry-- == 0) {
676 v = temporary_arg_v_store = UNCONST(he.str);
677 goto jleave;
680 v = NULL;
682 goto jleave;
684 # endif /* HAVE_HISTORY */
685 #endif /* HAVE_EDITLINE */
688 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
690 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
691 * We do not handle character widths because the terminal must deal with that
692 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
693 * characters by definition on the other. We're addicted.
695 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
696 * we're forced to use the very same buffer--the one that is passed through to
697 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
698 * convert that on-the-fly back to the plain char* result once we're done.
699 * To simplify our live, use savestr() buffers for all other needed memory
703 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
704 * TODO NCL: during handler de-/installation handling.
707 #ifdef HAVE_NCL
708 # ifndef MAX_INPUT
709 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
710 # endif
712 /* Since we simply fputs(3) the prompt, assume each character requires two
713 * visual cells -- and we need to restrict the maximum prompt size because
714 * of MAX_INPUT and our desire to have room for some error message left */
715 # define _PROMPT_VLEN(P) (strlen(P) * 2)
716 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
718 union xsighdl {
719 sighandler_type shdl; /* Try avoid races by setting */
720 sl_it sint; /* .sint=-1 when inactive */
722 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
724 struct xtios {
725 struct termios told;
726 struct termios tnew;
729 struct cell {
730 wchar_t wc;
731 ui32_t count;
732 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
735 struct line {
736 size_t cursor; /* Current cursor position */
737 size_t topins; /* Outermost cursor col set */
738 union {
739 char * cbuf; /* *x_buf */
740 struct cell * cells;
741 } line;
742 struct str defc; /* Current default content */
743 struct str savec; /* Saved default content */
744 # ifdef HAVE_HISTORY
745 struct hist * hist; /* History cursor */
746 # endif
747 char const * prompt;
748 char const * nd; /* Cursor right */
749 char ** x_buf; /* Caller pointers */
750 size_t * x_bufsize;
753 # ifdef HAVE_HISTORY
754 struct hist {
755 struct hist * older;
756 struct hist * younger;
757 size_t len;
758 char dat[VFIELD_SIZE(sizeof(size_t))];
760 # endif
762 static union xsighdl _ncl_oint;
763 static union xsighdl _ncl_oquit;
764 static union xsighdl _ncl_oterm;
765 static union xsighdl _ncl_ohup;
766 static union xsighdl _ncl_otstp;
767 static union xsighdl _ncl_ottin;
768 static union xsighdl _ncl_ottou;
769 static struct xtios _ncl_tios;
770 # ifdef HAVE_HISTORY
771 static struct hist * _ncl_hist;
772 static struct hist * _ncl_hist_tail;
773 static size_t _ncl_hist_size;
774 static size_t _ncl_hist_size_max;
775 static bool_t _ncl_hist_load;
776 # endif
778 static void _ncl_sigs_up(void);
779 static void _ncl_sigs_down(void);
781 static void _ncl_term_mode(bool_t raw);
783 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
784 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
785 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
786 static ssize_t _ncl_cell2dat(struct line *l);
787 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
788 static void _ncl_cell2save(struct line *l);
789 # endif
791 static void _ncl_khome(struct line *l, bool_t dobell);
792 static void _ncl_kend(struct line *l);
793 static void _ncl_kbs(struct line *l);
794 static void _ncl_kkill(struct line *l, bool_t dobell);
795 static ssize_t _ncl_keof(struct line *l);
796 static void _ncl_kleft(struct line *l);
797 static void _ncl_kright(struct line *l);
798 static void _ncl_krefresh(struct line *l);
799 static void _ncl_kbwddelw(struct line *l);
800 static void _ncl_kgow(struct line *l, ssize_t dir);
801 static void _ncl_kother(struct line *l, wchar_t wc);
802 # ifdef HAVE_HISTORY
803 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
804 static size_t _ncl_khist(struct line *l, bool_t backwd);
805 static size_t _ncl_krhist(struct line *l);
806 # endif
807 # ifdef HAVE_TABEXPAND
808 static size_t _ncl_kht(struct line *l);
809 # endif
810 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
811 size_t len SMALLOC_DEBUG_ARGS);
813 static void
814 _ncl_sigs_up(void)
816 NYD_ENTER;
817 if (_ncl_oint.sint == -1)
818 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
819 if (_ncl_oquit.sint == -1)
820 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
821 if (_ncl_oterm.sint == -1)
822 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
823 if (_ncl_ohup.sint == -1)
824 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
825 if (_ncl_otstp.sint == -1)
826 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
827 if (_ncl_ottin.sint == -1)
828 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
829 if (_ncl_ottou.sint == -1)
830 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
831 NYD_LEAVE;
834 static void
835 _ncl_sigs_down(void)
837 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
838 sighandler_type st;
839 NYD_ENTER;
841 if (_ncl_ottou.sint != -1) {
842 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
843 safe_signal(SIGTTOU, st);
845 if (_ncl_ottin.sint != -1) {
846 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
847 safe_signal(SIGTTIN, st);
849 if (_ncl_otstp.sint != -1) {
850 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
851 safe_signal(SIGTSTP, st);
853 if (_ncl_ohup.sint != -1) {
854 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
855 safe_signal(SIGHUP, st);
857 if (_ncl_oterm.sint != -1) {
858 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
859 safe_signal(SIGTERM, st);
861 if (_ncl_oquit.sint != -1) {
862 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
863 safe_signal(SIGQUIT, st);
865 if (_ncl_oint.sint != -1) {
866 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
867 safe_signal(SIGINT, st);
869 NYD_LEAVE;
872 static void
873 _ncl_term_mode(bool_t raw)
875 struct termios *tiosp = &_ncl_tios.told;
876 NYD_ENTER;
878 if (!raw)
879 goto jleave;
881 /* Always requery the attributes, in case we've been moved from background
882 * to foreground or however else in between sessions */
883 tcgetattr(STDIN_FILENO, tiosp);
884 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
885 tiosp = &_ncl_tios.tnew;
886 tiosp->c_cc[VMIN] = 1;
887 tiosp->c_cc[VTIME] = 0;
888 tiosp->c_iflag &= ~(ISTRIP);
889 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
890 jleave:
891 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
892 NYD_LEAVE;
895 static void
896 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
898 size_t i;
899 NYD_ENTER;
901 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
902 if (i > *l->x_bufsize) {
903 i <<= 1;
904 *l->x_bufsize = i;
905 l->line.cbuf =
906 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
908 NYD_LEAVE;
911 static void
912 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
914 size_t j;
915 NYD_ENTER;
917 if (i > 0)
918 memmove(cap, cap + 1, i * sizeof(*cap));
920 /* And.. the (rest of the) visual update */
921 for (j = 0; j < i; ++j)
922 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
923 fputs(" \b", stdout);
924 for (j = 0; j < i; ++j)
925 putchar('\b');
926 NYD_LEAVE;
929 static ssize_t
930 _ncl_wboundary(struct line *l, ssize_t dir)
932 size_t c, t, i;
933 struct cell *cap;
934 bool_t anynon;
935 NYD_ENTER;
937 c = l->cursor;
938 t = l->topins;
939 i = (size_t)-1;
941 if (dir < 0) {
942 if (c == 0)
943 goto jleave;
944 } else if (c == t)
945 goto jleave;
946 else
947 --t, --c; /* Unsigned wrapping may occur (twice), then */
949 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
950 wchar_t wc = cap[c + dir].wc;
951 if (iswblank(wc) || iswpunct(wc)) {
952 if (anynon)
953 break;
954 } else
955 anynon = TRU1;
956 ++i;
957 c += dir;
958 if (dir < 0) {
959 if (c == 0)
960 break;
961 } else if (c == t)
962 break;
964 jleave:
965 NYD_LEAVE;
966 return (ssize_t)i;
969 static ssize_t
970 _ncl_cell2dat(struct line *l)
972 size_t len = 0, i;
973 NYD_ENTER;
975 if (l->topins > 0)
976 for (i = 0; i < l->topins; ++i) {
977 struct cell *cap = l->line.cells + i;
978 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
979 len += cap->count;
981 l->line.cbuf[len] = '\0';
982 NYD_LEAVE;
983 return (ssize_t)len;
986 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
987 static void
988 _ncl_cell2save(struct line *l)
990 size_t len, i;
991 struct cell *cap;
992 NYD_ENTER;
994 l->savec.s = NULL, l->savec.l = 0;
995 if (l->topins == 0)
996 goto jleave;
998 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
999 len += cap->count;
1001 l->savec.l = len;
1002 l->savec.s = salloc(len + 1);
1004 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
1005 memcpy(l->savec.s + len, cap->cbuf, cap->count);
1006 len += cap->count;
1008 l->savec.s[len] = '\0';
1009 jleave:
1010 NYD_LEAVE;
1012 # endif
1014 static void
1015 _ncl_khome(struct line *l, bool_t dobell)
1017 size_t c;
1018 NYD_ENTER;
1020 c = l->cursor;
1021 if (c > 0) {
1022 l->cursor = 0;
1023 while (c-- != 0)
1024 putchar('\b');
1025 } else if (dobell)
1026 putchar('\a');
1027 NYD_LEAVE;
1030 static void
1031 _ncl_kend(struct line *l)
1033 ssize_t i;
1034 NYD_ENTER;
1036 i = (ssize_t)(l->topins - l->cursor);
1038 if (i > 0) {
1039 l->cursor = l->topins;
1040 while (i-- != 0)
1041 fputs(l->nd, stdout);
1042 } else
1043 putchar('\a');
1044 NYD_LEAVE;
1047 static void
1048 _ncl_kbs(struct line *l)
1050 ssize_t c, t;
1051 NYD_ENTER;
1053 c = l->cursor;
1054 t = l->topins;
1056 if (c > 0) {
1057 putchar('\b');
1058 l->cursor = --c;
1059 l->topins = --t;
1060 t -= c;
1061 _ncl_bs_eof_dvup(l->line.cells + c, t);
1062 } else
1063 putchar('\a');
1064 NYD_LEAVE;
1067 static void
1068 _ncl_kkill(struct line *l, bool_t dobell)
1070 size_t j, c, i;
1071 NYD_ENTER;
1073 c = l->cursor;
1074 i = (size_t)(l->topins - c);
1076 if (i > 0) {
1077 l->topins = c;
1078 for (j = i; j != 0; --j)
1079 putchar(' ');
1080 for (j = i; j != 0; --j)
1081 putchar('\b');
1082 } else if (dobell)
1083 putchar('\a');
1084 NYD_LEAVE;
1087 static ssize_t
1088 _ncl_keof(struct line *l)
1090 size_t c, t;
1091 ssize_t i;
1092 NYD_ENTER;
1094 c = l->cursor;
1095 t = l->topins;
1096 i = (ssize_t)(t - c);
1098 if (i > 0) {
1099 l->topins = --t;
1100 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1101 } else if (t == 0 && !ok_blook(ignoreeof)) {
1102 fputs("^D", stdout);
1103 fflush(stdout);
1104 i = -1;
1105 } else {
1106 putchar('\a');
1107 i = 0;
1109 NYD_LEAVE;
1110 return i;
1113 static void
1114 _ncl_kleft(struct line *l)
1116 NYD_ENTER;
1117 if (l->cursor > 0) {
1118 --l->cursor;
1119 putchar('\b');
1120 } else
1121 putchar('\a');
1122 NYD_LEAVE;
1125 static void
1126 _ncl_kright(struct line *l)
1128 NYD_ENTER;
1129 if (l->cursor < l->topins) {
1130 ++l->cursor;
1131 fputs(l->nd, stdout);
1132 } else
1133 putchar('\a');
1134 NYD_LEAVE;
1137 static void
1138 _ncl_krefresh(struct line *l)
1140 struct cell *cap;
1141 size_t i;
1142 NYD_ENTER;
1144 putchar('\r');
1145 if (l->prompt != NULL && *l->prompt != '\0')
1146 fputs(l->prompt, stdout);
1147 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1148 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1149 for (i = l->topins - l->cursor; i > 0; --i)
1150 putchar('\b');
1151 NYD_LEAVE;
1154 static void
1155 _ncl_kbwddelw(struct line *l)
1157 ssize_t i;
1158 size_t c, t, j;
1159 struct cell *cap;
1160 NYD_ENTER;
1162 i = _ncl_wboundary(l, -1);
1163 if (i <= 0) {
1164 if (i < 0)
1165 putchar('\a');
1166 goto jleave;
1169 c = l->cursor - i;
1170 t = l->topins;
1171 l->topins = t - i;
1172 l->cursor = c;
1173 cap = l->line.cells + c;
1175 if (t != l->cursor) {
1176 j = t - c + i;
1177 memmove(cap, cap + i, j * sizeof(*cap));
1180 for (j = i; j > 0; --j)
1181 putchar('\b');
1182 for (j = l->topins - c; j > 0; ++cap, --j)
1183 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1184 for (j = i; j > 0; --j)
1185 putchar(' ');
1186 for (j = t - c; j > 0; --j)
1187 putchar('\b');
1188 jleave:
1189 NYD_LEAVE;
1192 static void
1193 _ncl_kgow(struct line *l, ssize_t dir)
1195 ssize_t i;
1196 NYD_ENTER;
1198 i = _ncl_wboundary(l, dir);
1199 if (i <= 0) {
1200 if (i < 0)
1201 putchar('\a');
1202 goto jleave;
1205 if (dir < 0) {
1206 l->cursor -= i;
1207 while (i-- > 0)
1208 putchar('\b');
1209 } else {
1210 l->cursor += i;
1211 while (i-- > 0)
1212 fputs(l->nd, stdout);
1214 jleave:
1215 NYD_LEAVE;
1218 static void
1219 _ncl_kother(struct line *l, wchar_t wc)
1221 /* Append if at EOL, insert otherwise;
1222 * since we may move around character-wise, always use a fresh ps */
1223 mbstate_t ps;
1224 struct cell cell, *cap;
1225 size_t i, c;
1226 NYD_ENTER;
1228 /* First init a cell and see wether we'll really handle this wc */
1229 cell.wc = wc;
1230 memset(&ps, 0, sizeof ps);
1231 i = wcrtomb(cell.cbuf, wc, &ps);
1232 if (i > MB_LEN_MAX)
1233 goto jleave;
1234 cell.count = (ui_it)i;
1235 if (enc_has_state) {
1236 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1237 if (i == 1)
1239 else if (--i < MB_LEN_MAX)
1240 cell.count += (ui_it)i;
1241 else
1242 goto jleave;
1245 /* Yes, we will! Place it in the array */
1246 c = l->cursor++;
1247 i = l->topins++ - c;
1248 cap = l->line.cells + c;
1249 if (i > 0)
1250 memmove(cap + 1, cap, i * sizeof(cell));
1251 memcpy(cap, &cell, sizeof cell);
1253 /* And update visual */
1254 c = i;
1256 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1257 while ((++cap, i-- != 0));
1258 while (c-- != 0)
1259 putchar('\b');
1260 jleave:
1261 NYD_LEAVE;
1264 # ifdef HAVE_HISTORY
1265 static size_t
1266 __ncl_khist_shared(struct line *l, struct hist *hp)
1268 size_t rv;
1269 NYD_ENTER;
1271 if ((l->hist = hp) != NULL) {
1272 l->defc.s = savestrbuf(hp->dat, hp->len);
1273 rv =
1274 l->defc.l = hp->len;
1275 if (l->topins > 0) {
1276 _ncl_khome(l, FAL0);
1277 _ncl_kkill(l, FAL0);
1279 } else {
1280 putchar('\a');
1281 rv = 0;
1283 NYD_LEAVE;
1284 return rv;
1287 static size_t
1288 _ncl_khist(struct line *l, bool_t backwd)
1290 struct hist *hp;
1291 size_t rv;
1292 NYD_ENTER;
1294 /* If we're not in history mode yet, save line content;
1295 * also, disallow forward search, then, and, of course, bail unless we
1296 * do have any history at all */
1297 if ((hp = l->hist) == NULL) {
1298 if (!backwd)
1299 goto jleave;
1300 if ((hp = _ncl_hist) == NULL)
1301 goto jleave;
1302 _ncl_cell2save(l);
1303 goto jleave;
1306 hp = backwd ? hp->older : hp->younger;
1307 jleave:
1308 rv = __ncl_khist_shared(l, hp);
1309 NYD_LEAVE;
1310 return rv;
1313 static size_t
1314 _ncl_krhist(struct line *l)
1316 struct str orig_savec;
1317 struct hist *hp = NULL;
1318 size_t rv;
1319 NYD_ENTER;
1321 /* We cannot complete an empty line */
1322 if (l->topins == 0) {
1323 /* XXX The upcoming hard reset would restore a set savec buffer,
1324 * XXX so forcefully reset that. A cleaner solution would be to
1325 * XXX reset it whenever a restore is no longer desired */
1326 l->savec.s = NULL, l->savec.l = 0;
1327 goto jleave;
1329 if ((hp = l->hist) == NULL) {
1330 if ((hp = _ncl_hist) == NULL)
1331 goto jleave;
1332 orig_savec.s = NULL;
1333 orig_savec.l = 0; /* silence CC */
1334 } else if ((hp = hp->older) == NULL)
1335 goto jleave;
1336 else
1337 orig_savec = l->savec;
1339 if (orig_savec.s == NULL)
1340 _ncl_cell2save(l);
1341 for (; hp != NULL; hp = hp->older)
1342 if (is_prefix(l->savec.s, hp->dat))
1343 break;
1344 if (orig_savec.s != NULL)
1345 l->savec = orig_savec;
1346 jleave:
1347 rv = __ncl_khist_shared(l, hp);
1348 NYD_LEAVE;
1349 return rv;
1351 # endif
1353 # ifdef HAVE_TABEXPAND
1354 static size_t
1355 _ncl_kht(struct line *l)
1357 struct str orig, bot, topp, sub, exp;
1358 struct cell *cword, *ctop, *cx;
1359 bool_t set_savec = FAL0;
1360 size_t rv = 0;
1361 NYD_ENTER;
1363 /* We cannot expand an empty line */
1364 if (l->topins == 0)
1365 goto jleave;
1367 /* Get plain line data; if this is the first expansion/xy, update the
1368 * very original content so that ^G gets the origin back */
1369 orig = l->savec;
1370 _ncl_cell2save(l);
1371 exp = l->savec;
1372 if (orig.s != NULL)
1373 l->savec = orig;
1374 else
1375 set_savec = TRU1;
1376 orig = exp;
1378 cword = l->line.cells;
1379 ctop = cword + l->cursor;
1381 /* topp: separate data right of cursor */
1382 if ((cx = cword + l->topins) != ctop) {
1383 for (rv = 0; cx > ctop; --cx)
1384 rv += cx->count;
1385 topp.l = rv;
1386 topp.s = orig.s + orig.l - rv;
1387 } else
1388 topp.s = NULL, topp.l = 0;
1390 /* bot, sub: we cannot expand the entire data left of cursor, but only
1391 * the last "word", so separate them */
1392 while (cx > cword && !iswspace(cx[-1].wc))
1393 --cx;
1394 for (rv = 0; cword < cx; ++cword)
1395 rv += cword->count;
1396 sub =
1397 bot = orig;
1398 bot.l = rv;
1399 sub.s += rv;
1400 sub.l -= rv;
1401 sub.l -= topp.l;
1403 /* Leave room for "implicit asterisk" expansion, as below */
1404 if (sub.l == 0) {
1405 sub.s = UNCONST("*");
1406 sub.l = 1;
1407 } else {
1408 exp.s = salloc(sub.l + 1 +1);
1409 memcpy(exp.s, sub.s, sub.l);
1410 exp.s[sub.l] = '\0';
1411 sub.s = exp.s;
1414 /* TODO there is a TODO note upon fexpand() with multi-return;
1415 * TODO if that will change, the if() below can be simplified */
1416 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1417 jredo:
1418 hold_all_sigs();
1419 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1420 rele_all_sigs();
1422 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1423 goto jnope;
1424 /* If the expansion equals the original string, assume the user wants what
1425 * is usually known as tab completion, append `*' and restart */
1426 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1427 if (sub.s[sub.l - 1] == '*')
1428 goto jnope;
1429 sub.s[sub.l++] = '*';
1430 sub.s[sub.l] = '\0';
1431 goto jredo;
1434 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1435 * Take care to take *prompt* into account, since we don't know
1436 * anything about it's visual length (fputs(3) is used), simply
1437 * assume each character requires two columns */
1438 /* TODO the problem is that we loose control otherwise; in the best
1439 * TODO case the user can control via ^A and ^K etc., but be safe;
1440 * TODO we cannot simply adjust fexpand() because we don't know how
1441 * TODO that is implemented... The real solution would be to check
1442 * TODO wether we fit on a line, and start a pager if not.
1443 * TODO However, that should be part of a real tab-COMPLETION, then,
1444 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1445 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1446 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1447 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1448 char const e1[] = "[maximum line size exceeded]";
1449 exp.s = UNCONST(e1);
1450 exp.l = sizeof(e1) - 1;
1451 topp.l = 0;
1452 if (rv + bot.l + exp.l >= MAX_INPUT)
1453 bot.l = 0;
1454 if (rv + exp.l >= MAX_INPUT) {
1455 char const e2[] = "[ERR]";
1456 exp.s = UNCONST(e2);
1457 exp.l = sizeof(e2) - 1;
1461 orig.l = bot.l + exp.l + topp.l;
1462 orig.s = salloc(orig.l + 1 + 5);
1463 if ((rv = bot.l) > 0)
1464 memcpy(orig.s, bot.s, rv);
1465 memcpy(orig.s + rv, exp.s, exp.l);
1466 rv += exp.l;
1467 if (topp.l > 0) {
1468 memcpy(orig.s + rv, topp.s, topp.l);
1469 rv += topp.l;
1471 orig.s[rv] = '\0';
1473 l->defc = orig;
1474 _ncl_khome(l, FAL0);
1475 _ncl_kkill(l, FAL0);
1476 jleave:
1477 NYD_LEAVE;
1478 return rv;
1479 jnope:
1480 /* If we've provided a default content, but failed to expand, there is
1481 * nothing we can "revert to": drop that default again */
1482 if (set_savec)
1483 l->savec.s = NULL, l->savec.l = 0;
1484 rv = 0;
1485 goto jleave;
1487 # endif /* HAVE_TABEXPAND */
1489 static ssize_t
1490 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1491 SMALLOC_DEBUG_ARGS)
1493 /* We want to save code, yet we may have to incorporate a lines'
1494 * default content and / or default input to switch back to after some
1495 * history movement; let "len > 0" mean "have to display some data
1496 * buffer", and only otherwise read(2) it */
1497 mbstate_t ps[2];
1498 struct line l;
1499 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
1500 wchar_t wc;
1501 ssize_t rv;
1502 ui32_t maybe_cursor;
1503 NYD_ENTER;
1505 memset(&l, 0, sizeof l);
1506 l.line.cbuf = *buf;
1507 if (len != 0) {
1508 l.defc.s = savestrbuf(*buf, len);
1509 l.defc.l = len;
1511 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1512 l.prompt = prompt = "?ERR?";
1513 /* TODO *l.nd=='\0' only because we have no value-cache -> see acmava.c */
1514 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1515 l.nd = "\033[C"; /* XXX no "magic" constant */
1516 l.x_buf = buf;
1517 l.x_bufsize = bufsize;
1519 if (prompt != NULL && *prompt != '\0') {
1520 fputs(prompt, stdout);
1521 fflush(stdout);
1523 jrestart:
1524 memset(ps, 0, sizeof ps);
1525 maybe_cursor = 0;
1526 /* TODO: NCL: we should output the reset sequence when we jrestart:
1527 * TODO: NCL: if we are using a stateful encoding? !
1528 * TODO: NCL: in short: this is not yet well understood */
1529 for (;;) {
1530 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1532 /* Normal read(2)? Else buffer-takeover: speed this one up */
1533 if (len == 0)
1534 cbufp =
1535 cbuf = cbuf_base;
1536 else {
1537 assert(l.defc.l > 0 && l.defc.s != NULL);
1538 cbufp =
1539 cbuf = l.defc.s + (l.defc.l - len);
1540 cbufp += len;
1543 /* Read in the next complete multibyte character */
1544 for (;;) {
1545 if (len == 0) {
1546 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1547 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1548 continue;
1549 goto jleave;
1551 ++cbufp;
1554 /* Ach! the ISO C multibyte handling!
1555 * Encodings with locking shift states cannot really be helped, since
1556 * it is impossible to only query the shift state, as opposed to the
1557 * entire shift state + character pair (via ISO C functions) */
1558 rv = (ssize_t)mbrtowc(&wc, cbuf, (size_t)(cbufp - cbuf), ps + 0);
1559 if (rv <= 0) {
1560 /* Any error during take-over can only result in a hard reset;
1561 * Otherwise, if it's a hard error, or if too many redundant shift
1562 * sequences overflow our buffer, also perform a hard reset */
1563 if (len != 0 || rv == -1 ||
1564 sizeof cbuf_base == (size_t)(cbufp - cbuf)) {
1565 l.savec.s = l.defc.s = NULL,
1566 l.savec.l = l.defc.l = len = 0;
1567 putchar('\a');
1568 wc = 'G';
1569 goto jreset;
1571 /* Otherwise, due to the way we deal with the buffer, we need to
1572 * restore the mbstate_t from before this conversion */
1573 ps[0] = ps[1];
1574 continue;
1577 if (len != 0 && (len -= (size_t)rv) == 0)
1578 l.defc.s = NULL, l.defc.l = 0;
1579 ps[1] = ps[0];
1580 break;
1583 /* Don't interpret control bytes during buffer take-over */
1584 if (cbuf != cbuf_base)
1585 goto jprint;
1586 switch (wc) {
1587 case 'A' ^ 0x40: /* cursor home */
1588 _ncl_khome(&l, TRU1);
1589 break;
1590 j_b:
1591 case 'B' ^ 0x40: /* backward character */
1592 _ncl_kleft(&l);
1593 break;
1594 /* 'C': interrupt (CTRL-C) */
1595 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1596 if ((rv = _ncl_keof(&l)) < 0)
1597 goto jleave;
1598 break;
1599 case 'E' ^ 0x40: /* end of line */
1600 _ncl_kend(&l);
1601 break;
1602 j_f:
1603 case 'F' ^ 0x40: /* forward character */
1604 _ncl_kright(&l);
1605 break;
1606 /* 'G' below */
1607 case 'H' ^ 0x40: /* backspace */
1608 case '\177':
1609 _ncl_kbs(&l);
1610 break;
1611 case 'I' ^ 0x40: /* horizontal tab */
1612 # ifdef HAVE_TABEXPAND
1613 if ((len = _ncl_kht(&l)) > 0)
1614 goto jrestart;
1615 # endif
1616 goto jbell;
1617 case 'J' ^ 0x40: /* NL (\n) */
1618 goto jdone;
1619 case 'G' ^ 0x40: /* full reset */
1620 jreset:
1621 /* FALLTHRU */
1622 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1623 _ncl_khome(&l, FAL0);
1624 /* FALLTHRU */
1625 case 'K' ^ 0x40: /* kill from cursor to end of line */
1626 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1627 /* (Handle full reset?) */
1628 if (wc == ('G' ^ 0x40)) {
1629 # ifdef HAVE_HISTORY
1630 l.hist = NULL;
1631 # endif
1632 if ((len = l.savec.l) != 0) {
1633 l.defc = l.savec;
1634 l.savec.s = NULL, l.savec.l = 0;
1635 } else
1636 len = l.defc.l;
1638 fflush(stdout);
1639 goto jrestart;
1640 case 'L' ^ 0x40: /* repaint line */
1641 _ncl_krefresh(&l);
1642 break;
1643 /* 'M': CR (\r) */
1644 j_n:
1645 case 'N' ^ 0x40: /* history next */
1646 # ifdef HAVE_HISTORY
1647 if (l.hist == NULL)
1648 goto jbell;
1649 if ((len = _ncl_khist(&l, FAL0)) > 0)
1650 goto jrestart;
1651 wc = 'G' ^ 0x40;
1652 goto jreset;
1653 # else
1654 goto jbell;
1655 # endif
1656 /* 'O' */
1657 j_p:
1658 case 'P' ^ 0x40: /* history previous */
1659 # ifdef HAVE_HISTORY
1660 if ((len = _ncl_khist(&l, TRU1)) > 0)
1661 goto jrestart;
1662 wc = 'G' ^ 0x40;
1663 goto jreset;
1664 # else
1665 goto jbell;
1666 # endif
1667 /* 'Q': no code */
1668 case 'R' ^ 0x40: /* reverse history search */
1669 # ifdef HAVE_HISTORY
1670 if ((len = _ncl_krhist(&l)) > 0)
1671 goto jrestart;
1672 wc = 'G' ^ 0x40;
1673 goto jreset;
1674 # else
1675 goto jbell;
1676 # endif
1677 /* 'S': no code */
1678 /* 'U' above */
1679 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1680 case 'W' ^ 0x40: /* backward delete "word" */
1681 _ncl_kbwddelw(&l);
1682 break;
1683 case 'X' ^ 0x40: /* move cursor forward "word" */
1684 _ncl_kgow(&l, +1);
1685 break;
1686 case 'Y' ^ 0x40: /* move cursor backward "word" */
1687 _ncl_kgow(&l, -1);
1688 break;
1689 /* 'Z': suspend (CTRL-Z) */
1690 case 0x1B:
1691 if (maybe_cursor++ != 0)
1692 goto jreset;
1693 continue;
1694 default:
1695 /* XXX Handle usual ^[[[ABCD] cursor keys -- UGLY, "MAGIC", INFLEX */
1696 if (maybe_cursor > 0) {
1697 if (++maybe_cursor == 2) {
1698 if (wc == L'[')
1699 continue;
1700 maybe_cursor = 0;
1701 } else {
1702 maybe_cursor = 0;
1703 switch (wc) {
1704 case L'A': goto j_p;
1705 case L'B': goto j_n;
1706 case L'C': goto j_f;
1707 case L'D': goto j_b;
1709 _ncl_kother(&l, L'[');
1712 jprint:
1713 if (iswprint(wc)) {
1714 _ncl_kother(&l, wc);
1715 /* Don't clear the history during takeover..
1716 * ..and also avoid fflush()ing unless we've
1717 * worked the entire buffer */
1718 if (len > 0)
1719 continue;
1720 # ifdef HAVE_HISTORY
1721 if (cbuf == cbuf_base)
1722 l.hist = NULL;
1723 # endif
1724 } else {
1725 jbell:
1726 putchar('\a');
1728 break;
1730 fflush(stdout);
1733 /* We have a completed input line, convert the struct cell data to its
1734 * plain character equivalent */
1735 jdone:
1736 putchar('\n');
1737 fflush(stdout);
1738 len = _ncl_cell2dat(&l);
1739 rv = (ssize_t)len;
1740 jleave:
1741 NYD_LEAVE;
1742 return rv;
1745 FL void
1746 tty_init(void)
1748 # ifdef HAVE_HISTORY
1749 long hs;
1750 char *v, *lbuf;
1751 FILE *f;
1752 size_t lsize, cnt, llen;
1753 # endif
1754 NYD_ENTER;
1756 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1757 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1758 _ncl_ottou.sint = -1;
1760 # ifdef HAVE_HISTORY
1761 _CL_HISTSIZE(hs);
1762 _ncl_hist_size = 0;
1763 _ncl_hist_size_max = hs;
1764 if (hs == 0)
1765 goto jleave;
1767 _CL_HISTFILE(v);
1768 if (v == NULL)
1769 goto jleave;
1771 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1772 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1773 if (f == NULL)
1774 goto jdone;
1775 fcntl_lock(fileno(f), FLOCK_READ); /* TODO ouch, retval check, etc. */
1777 lbuf = NULL;
1778 lsize = 0;
1779 cnt = fsize(f);
1780 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1781 if (llen > 0 && lbuf[llen - 1] == '\n')
1782 lbuf[--llen] = '\0';
1783 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1784 continue;
1785 _ncl_hist_load = TRU1;
1786 tty_addhist(lbuf);
1787 _ncl_hist_load = FAL0;
1789 if (lbuf != NULL)
1790 free(lbuf);
1792 fclose(f);
1793 jdone:
1794 rele_all_sigs(); /* XXX remove jumps */
1795 jleave:
1796 # endif /* HAVE_HISTORY */
1797 NYD_LEAVE;
1800 FL void
1801 tty_destroy(void)
1803 # ifdef HAVE_HISTORY
1804 long hs;
1805 char *v;
1806 struct hist *hp;
1807 FILE *f;
1808 # endif
1809 NYD_ENTER;
1811 # ifdef HAVE_HISTORY
1812 _CL_HISTSIZE(hs);
1813 if (hs == 0)
1814 goto jleave;
1816 _CL_HISTFILE(v);
1817 if (v == NULL)
1818 goto jleave;
1820 if ((hp = _ncl_hist) != NULL)
1821 while (hp->older != NULL && hs-- != 0)
1822 hp = hp->older;
1824 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1825 f = fopen(v, "w"); /* TODO temporary + rename?! */
1826 if (f == NULL)
1827 goto jdone;
1828 fcntl_lock(fileno(f), FLOCK_WRITE); /* TODO ouch, retval check, etc. */
1829 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1830 goto jclose;
1832 for (; hp != NULL; hp = hp->younger) {
1833 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1834 putc('\n', f);
1836 jclose:
1837 fclose(f);
1838 jdone:
1839 rele_all_sigs(); /* XXX remove jumps */
1840 jleave:
1841 # endif /* HAVE_HISTORY */
1842 NYD_LEAVE;
1845 FL void
1846 tty_signal(int sig)
1848 sigset_t nset, oset;
1849 NYD_X; /* Signal handler */
1851 switch (sig) {
1852 case SIGWINCH:
1853 /* We don't deal with SIGWINCH, yet get called from main.c */
1854 break;
1855 default:
1856 _ncl_term_mode(FAL0);
1857 _ncl_sigs_down();
1858 sigemptyset(&nset);
1859 sigaddset(&nset, sig);
1860 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1861 kill(0, sig);
1862 /* When we come here we'll continue editing, so reestablish */
1863 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1864 _ncl_sigs_up();
1865 _ncl_term_mode(TRU1);
1866 break;
1870 FL int
1871 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1872 SMALLOC_DEBUG_ARGS)
1874 ssize_t nn;
1875 NYD_ENTER;
1877 /* Of course we have races here, but they cannot be avoided on POSIX
1878 * (except by even *more* actions) */
1879 _ncl_sigs_up();
1880 _ncl_term_mode(TRU1);
1881 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1882 _ncl_term_mode(FAL0);
1883 _ncl_sigs_down();
1884 NYD_LEAVE;
1885 return (int)nn;
1888 FL void
1889 tty_addhist(char const *s)
1891 # ifdef HAVE_HISTORY
1892 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1893 size_t l;
1894 struct hist *h, *o, *y;
1895 # endif
1896 NYD_ENTER;
1897 UNUSED(s);
1899 # ifdef HAVE_HISTORY
1900 l = strlen(s);
1902 if (_ncl_hist_size_max == 0)
1903 goto j_leave;
1904 _CL_CHECK_ADDHIST(s, goto j_leave);
1906 /* Eliminating duplicates is expensive, but simply inacceptable so
1907 * during the load of a potentially large history file! */
1908 if (!_ncl_hist_load)
1909 for (h = _ncl_hist; h != NULL; h = h->older)
1910 if (h->len == l && !strcmp(h->dat, s)) {
1911 hold_all_sigs(); /* TODO */
1912 o = h->older;
1913 y = h->younger;
1914 if (o != NULL)
1915 o->younger = y;
1916 else
1917 _ncl_hist_tail = y;
1918 if (y != NULL)
1919 y->older = o;
1920 else
1921 _ncl_hist = o;
1922 goto jleave;
1924 hold_all_sigs();
1926 ++_ncl_hist_size;
1927 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1928 --_ncl_hist_size;
1929 if ((h = _ncl_hist_tail) != NULL) {
1930 if ((_ncl_hist_tail = h->younger) == NULL)
1931 _ncl_hist = NULL;
1932 else
1933 _ncl_hist_tail->older = NULL;
1934 free(h);
1938 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1939 h->len = l;
1940 memcpy(h->dat, s, l +1);
1941 jleave:
1942 if ((h->older = _ncl_hist) != NULL)
1943 _ncl_hist->younger = h;
1944 else
1945 _ncl_hist_tail = h;
1946 h->younger = NULL;
1947 _ncl_hist = h;
1949 rele_all_sigs();
1950 j_leave:
1951 # endif
1952 NYD_LEAVE;
1955 # ifdef HAVE_HISTORY
1956 FL int
1957 c_history(void *v)
1959 C_HISTORY_SHARED;
1961 jlist: {
1962 FILE *fp;
1963 size_t i, b;
1964 struct hist *h;
1966 if (_ncl_hist == NULL)
1967 goto jleave;
1969 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1970 NULL) {
1971 perror("tmpfile");
1972 v = NULL;
1973 goto jleave;
1976 i = _ncl_hist_size;
1977 b = 0;
1978 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
1979 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
1980 (ul_it)i, h->dat, (ul_it)b, (ul_it)h->len);
1982 page_or_print(fp, i);
1983 Fclose(fp);
1985 goto jleave;
1987 jclear: {
1988 struct hist *h;
1989 while ((h = _ncl_hist) != NULL) {
1990 _ncl_hist = h->older;
1991 free(h);
1993 _ncl_hist_tail = NULL;
1994 _ncl_hist_size = 0;
1996 goto jleave;
1998 jentry: {
1999 struct hist *h = _ncl_hist;
2000 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2001 entry = (long)_ncl_hist_size - entry;
2002 for (h = _ncl_hist;; h = h->older)
2003 if (h == NULL)
2004 break;
2005 else if (entry-- != 0)
2006 continue;
2007 else {
2008 v = temporary_arg_v_store = h->dat;
2009 goto jleave;
2012 v = NULL;
2014 goto jleave;
2016 # endif /* HAVE_HISTORY */
2017 #endif /* HAVE_NCL */
2020 * The really-nothing-at-all implementation
2023 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2024 FL void
2025 tty_init(void)
2027 NYD_ENTER;
2028 NYD_LEAVE;
2031 FL void
2032 tty_destroy(void)
2034 NYD_ENTER;
2035 NYD_LEAVE;
2038 FL void
2039 tty_signal(int sig)
2041 NYD_X; /* Signal handler */
2042 UNUSED(sig);
2045 FL int
2046 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2047 SMALLOC_DEBUG_ARGS)
2049 /* TODO The nothing-at-all tty layer even forces re-entering all the
2050 * TODO original data when re-editing a field */
2051 bool_t doffl = FAL0;
2052 int rv;
2053 NYD_ENTER;
2055 if (prompt != NULL && *prompt != '\0') {
2056 fputs(prompt, stdout);
2057 doffl = TRU1;
2059 if (n > 0) {
2060 fprintf(stdout, tr(511, "{former content: %.*s} "), (int)n, *linebuf);
2061 n = 0;
2062 doffl = TRU1;
2064 if (doffl)
2065 fflush(stdout);
2066 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2067 NYD_LEAVE;
2068 return rv;
2071 FL void
2072 tty_addhist(char const *s)
2074 NYD_ENTER;
2075 UNUSED(s);
2076 NYD_LEAVE;
2078 #endif /* nothing at all */
2080 /* vim:set fenc=utf-8:s-it-mode */