Introduce enum flock_type for fcntl_lock() (drop fcntl.h)
[s-mailx.git] / tty.c
blob9cd743abb097efd80deaeb420f58fcdf3229ef57
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") == 0)\
111 goto jlist;\
112 if (asccasecmp(*argv, "clear") == 0)\
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 = TRU1;
230 char *u = *user, *p = *pass;
231 NYD_ENTER;
233 if (u == NULL) {
234 if ((u = getuser(NULL)) == NULL)
235 rv = FAL0;
236 else if (p == NULL)
237 u = savestr(u);
238 *user = u;
241 if (p == NULL) {
242 if ((p = getpassword(NULL)) == NULL)
243 rv = FAL0;
244 *pass = p;
246 NYD_LEAVE;
247 return rv;
251 * readline(3)
254 #ifdef HAVE_READLINE
255 static sighandler_type _rl_shup;
256 static char * _rl_buf; /* pre_input() hook: initial line */
257 static int _rl_buflen; /* content, and its length */
259 static int _rl_pre_input(void);
261 static int
262 _rl_pre_input(void)
264 NYD_ENTER;
265 /* Handle leftover data from \ escaped former line */
266 rl_extend_line_buffer(_rl_buflen + 10);
267 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
268 rl_point = rl_end = _rl_buflen;
269 rl_pre_input_hook = (rl_hook_func_t*)NULL;
270 rl_redisplay();
271 NYD_LEAVE;
272 return 0;
275 FL void
276 tty_init(void)
278 # ifdef HAVE_HISTORY
279 long hs;
280 char *v;
281 # endif
282 NYD_ENTER;
284 rl_readline_name = UNCONST(uagent);
285 # ifdef HAVE_HISTORY
286 _CL_HISTSIZE(hs);
287 using_history();
288 stifle_history((int)hs);
289 # endif
290 rl_read_init_file(NULL);
292 /* Because rl_read_init_file() may have introduced yet a different
293 * history size limit, simply load and incorporate the history, leave
294 * it up to readline(3) to do the rest */
295 # ifdef HAVE_HISTORY
296 _CL_HISTFILE(v);
297 if (v != NULL)
298 read_history(v);
299 # endif
300 NYD_LEAVE;
303 FL void
304 tty_destroy(void)
306 # ifdef HAVE_HISTORY
307 char *v;
308 # endif
309 NYD_ENTER;
311 # ifdef HAVE_HISTORY
312 _CL_HISTFILE(v);
313 if (v != NULL)
314 write_history(v);
315 # endif
316 NYD_LEAVE;
319 FL void
320 tty_signal(int sig)
322 sigset_t nset, oset;
323 NYD_X; /* Signal handler */
325 switch (sig) {
326 # ifdef SIGWINCH
327 case SIGWINCH:
328 break;
329 # endif
330 case SIGHUP:
331 /* readline(3) doesn't catch it :( */
332 rl_free_line_state();
333 rl_cleanup_after_signal();
334 safe_signal(SIGHUP, _rl_shup);
335 sigemptyset(&nset);
336 sigaddset(&nset, sig);
337 sigprocmask(SIG_UNBLOCK, &nset, &oset);
338 kill(0, sig);
339 /* XXX When we come here we'll continue editing, so reestablish
340 * XXX cannot happen */
341 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
342 _rl_shup = safe_signal(SIGHUP, &tty_signal);
343 rl_reset_after_signal();
344 break;
345 default:
346 break;
350 FL int
351 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
352 SMALLOC_DEBUG_ARGS)
354 int nn;
355 char *line;
356 NYD_ENTER;
358 if (n > 0) {
359 _rl_buf = *linebuf;
360 _rl_buflen = (int)n;
361 rl_pre_input_hook = &_rl_pre_input;
364 _rl_shup = safe_signal(SIGHUP, &tty_signal);
365 line = readline(prompt != NULL ? prompt : "");
366 safe_signal(SIGHUP, _rl_shup);
368 if (line == NULL) {
369 nn = -1;
370 goto jleave;
372 n = strlen(line);
374 if (n >= *linesize) {
375 *linesize = LINESIZE + n + 1;
376 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
378 memcpy(*linebuf, line, n);
379 (free)(line);
380 (*linebuf)[n] = '\0';
381 nn = (int)n;
382 jleave:
383 NYD_LEAVE;
384 return nn;
387 FL void
388 tty_addhist(char const *s)
390 NYD_ENTER;
391 UNUSED(s);
392 # ifdef HAVE_HISTORY
393 _CL_CHECK_ADDHIST(s, goto jleave);
394 hold_all_sigs(); /* XXX too heavy */
395 add_history(s); /* XXX yet we jump away! */
396 rele_all_sigs(); /* XXX remove jumps */
397 jleave:
398 # endif
399 NYD_LEAVE;
402 # ifdef HAVE_HISTORY
403 FL int
404 c_history(void *v)
406 C_HISTORY_SHARED;
408 jlist: {
409 FILE *fp;
410 HISTORY_STATE *hs;
411 HIST_ENTRY **hl;
412 ul_it i, b;
414 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
415 NULL) {
416 perror("tmpfile");
417 v = NULL;
418 goto jleave;
421 hs = history_get_history_state();
423 for (i = (ul_it)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
424 char *cp = (*--hl)->line;
425 size_t sl = strlen(cp);
426 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n", i, cp, b, sl);
427 b += sl;
430 page_or_print(fp, (size_t)hs->length);
431 Fclose(fp);
433 goto jleave;
435 jclear:
436 clear_history();
437 goto jleave;
439 jentry: {
440 HISTORY_STATE *hs = history_get_history_state();
442 if (UICMP(z, entry, <=, hs->length))
443 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
444 else
445 v = NULL;
447 goto jleave;
449 # endif /* HAVE_HISTORY */
450 #endif /* HAVE_READLINE */
453 * BSD editline(3)
456 #ifdef HAVE_EDITLINE
457 static EditLine * _el_el; /* editline(3) handle */
458 static char const * _el_prompt; /* Current prompt */
459 # ifdef HAVE_HISTORY
460 static History * _el_hcom; /* History handle for commline */
461 # endif
463 static char const * _el_getprompt(void);
465 static char const *
466 _el_getprompt(void)
468 return _el_prompt;
471 FL void
472 tty_init(void)
474 # ifdef HAVE_HISTORY
475 HistEvent he;
476 long hs;
477 char *v;
478 # endif
479 NYD_ENTER;
481 # ifdef HAVE_HISTORY
482 _CL_HISTSIZE(hs);
483 _el_hcom = history_init();
484 history(_el_hcom, &he, H_SETSIZE, (int)hs);
485 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
486 # endif
488 _el_el = el_init(uagent, stdin, stdout, stderr);
489 el_set(_el_el, EL_SIGNAL, 1);
490 el_set(_el_el, EL_TERMINAL, NULL);
491 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
492 # ifdef HAVE_HISTORY
493 el_set(_el_el, EL_HIST, &history, _el_hcom);
494 # endif
495 el_set(_el_el, EL_EDITOR, "emacs");
496 # ifdef EL_PROMPT_ESC
497 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
498 # else
499 el_set(_el_el, EL_PROMPT, &_el_getprompt);
500 # endif
501 # if 0
502 el_set(_el_el, EL_ADDFN, "tab_complete",
503 "editline(3) internal completion function", &_el_file_cpl);
504 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
505 # endif
506 # ifdef HAVE_HISTORY
507 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
508 # endif
509 el_source(_el_el, NULL); /* Source ~/.editrc */
511 /* Because el_source() may have introduced yet a different history size
512 * limit, simply load and incorporate the history, leave it up to
513 * editline(3) to do the rest */
514 # ifdef HAVE_HISTORY
515 _CL_HISTFILE(v);
516 if (v != NULL)
517 history(_el_hcom, &he, H_LOAD, v);
518 # endif
519 NYD_LEAVE;
522 FL void
523 tty_destroy(void)
525 # ifdef HAVE_HISTORY
526 HistEvent he;
527 char *v;
528 # endif
529 NYD_ENTER;
531 el_end(_el_el);
533 # ifdef HAVE_HISTORY
534 _CL_HISTFILE(v);
535 if (v != NULL)
536 history(_el_hcom, &he, H_SAVE, v);
537 history_end(_el_hcom);
538 # endif
539 NYD_LEAVE;
542 FL void
543 tty_signal(int sig)
545 NYD_X; /* Signal handler */
546 switch (sig) {
547 # ifdef SIGWINCH
548 case SIGWINCH:
549 el_resize(_el_el);
550 break;
551 # endif
552 default:
553 break;
557 FL int
558 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
559 SMALLOC_DEBUG_ARGS)
561 int nn;
562 char const *line;
563 NYD_ENTER;
565 _el_prompt = (prompt != NULL) ? prompt : "";
566 if (n > 0)
567 el_push(_el_el, *linebuf);
568 line = el_gets(_el_el, &nn);
570 if (line == NULL) {
571 nn = -1;
572 goto jleave;
574 assert(nn >= 0);
575 n = (size_t)nn;
576 if (n > 0 && line[n - 1] == '\n')
577 nn = (int)--n;
579 if (n >= *linesize) {
580 *linesize = LINESIZE + n + 1;
581 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
583 memcpy(*linebuf, line, n);
584 (*linebuf)[n] = '\0';
585 jleave:
586 NYD_LEAVE;
587 return nn;
590 FL void
591 tty_addhist(char const *s)
593 # ifdef HAVE_HISTORY
594 /* Enlarge meaning of unique .. to something that rocks;
595 * xxx unfortunately this is expensive to do with editline(3)
596 * xxx maybe it would be better to hook the ptfs instead? */
597 HistEvent he;
598 int i;
599 # endif
600 NYD_ENTER;
601 UNUSED(s);
603 # ifdef HAVE_HISTORY
604 _CL_CHECK_ADDHIST(s, goto jleave);
606 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
607 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
608 i = history(_el_hcom, &he, H_NEXT))
609 if (strcmp(he.str, s) == 0) {
610 history(_el_hcom, &he, H_DEL, he.num);
611 break;
613 history(_el_hcom, &he, H_ENTER, s);
614 rele_all_sigs(); /* XXX remove jumps */
615 jleave:
616 # endif
617 NYD_LEAVE;
620 # ifdef HAVE_HISTORY
621 FL int
622 c_history(void *v)
624 C_HISTORY_SHARED;
626 jlist: {
627 HistEvent he;
628 FILE *fp;
629 size_t i, b;
630 int x;
632 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
633 NULL) {
634 perror("tmpfile");
635 v = NULL;
636 goto jleave;
639 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
640 b = 0;
641 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
642 x = history(_el_hcom, &he, H_NEXT)) {
643 size_t sl = strlen(he.str);
644 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
645 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
646 --i;
647 b += sl;
650 page_or_print(fp, i);
651 Fclose(fp);
653 goto jleave;
655 jclear: {
656 HistEvent he;
657 history(_el_hcom, &he, H_CLEAR);
659 goto jleave;
661 jentry: {
662 HistEvent he;
663 size_t i;
664 int x;
666 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
667 if (UICMP(z, entry, <=, i)) {
668 entry = (long)i - entry;
669 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
670 x = history(_el_hcom, &he, H_NEXT))
671 if (entry-- == 0) {
672 v = temporary_arg_v_store = UNCONST(he.str);
673 goto jleave;
676 v = NULL;
678 goto jleave;
680 # endif /* HAVE_HISTORY */
681 #endif /* HAVE_EDITLINE */
684 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
686 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
687 * We do not handle character widths because the terminal must deal with that
688 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
689 * characters by definition on the other. We're addicted.
691 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
692 * we're forced to use the very same buffer--the one that is passed through to
693 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
694 * convert that on-the-fly back to the plain char* result once we're done.
695 * To simplify our live, use savestr() buffers for all other needed memory
699 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
700 * TODO NCL: during handler de-/installation handling.
703 #ifdef HAVE_NCL
704 # ifndef MAX_INPUT
705 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
706 # endif
708 /* Since we simply fputs(3) the prompt, assume each character requires two
709 * visual cells -- and we need to restrict the maximum prompt size because
710 * of MAX_INPUT and our desire to have room for some error message left */
711 # define _PROMPT_VLEN(P) (strlen(P) * 2)
712 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
714 union xsighdl {
715 sighandler_type shdl; /* Try avoid races by setting */
716 sl_it sint; /* .sint=-1 when inactive */
718 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
720 struct xtios {
721 struct termios told;
722 struct termios tnew;
725 struct cell {
726 wchar_t wc;
727 ui32_t count;
728 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
731 struct line {
732 size_t cursor; /* Current cursor position */
733 size_t topins; /* Outermost cursor col set */
734 union {
735 char * cbuf; /* *x_buf */
736 struct cell * cells;
737 } line;
738 struct str defc; /* Current default content */
739 struct str savec; /* Saved default content */
740 # ifdef HAVE_HISTORY
741 struct hist * hist; /* History cursor */
742 # endif
743 char const * prompt;
744 char const * nd; /* Cursor right */
745 char ** x_buf; /* Caller pointers */
746 size_t * x_bufsize;
749 # ifdef HAVE_HISTORY
750 struct hist {
751 struct hist * older;
752 struct hist * younger;
753 size_t len;
754 char dat[VFIELD_SIZE(sizeof(size_t))];
756 # endif
758 static union xsighdl _ncl_oint;
759 static union xsighdl _ncl_oquit;
760 static union xsighdl _ncl_oterm;
761 static union xsighdl _ncl_ohup;
762 static union xsighdl _ncl_otstp;
763 static union xsighdl _ncl_ottin;
764 static union xsighdl _ncl_ottou;
765 static struct xtios _ncl_tios;
766 # ifdef HAVE_HISTORY
767 static struct hist * _ncl_hist;
768 static struct hist * _ncl_hist_tail;
769 static size_t _ncl_hist_size;
770 static size_t _ncl_hist_size_max;
771 static bool_t _ncl_hist_load;
772 # endif
774 static void _ncl_sigs_up(void);
775 static void _ncl_sigs_down(void);
777 static void _ncl_term_mode(bool_t raw);
779 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
780 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
781 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
782 static ssize_t _ncl_cell2dat(struct line *l);
783 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
784 static void _ncl_cell2save(struct line *l);
785 # endif
787 static void _ncl_khome(struct line *l, bool_t dobell);
788 static void _ncl_kend(struct line *l);
789 static void _ncl_kbs(struct line *l);
790 static void _ncl_kkill(struct line *l, bool_t dobell);
791 static ssize_t _ncl_keof(struct line *l);
792 static void _ncl_kleft(struct line *l);
793 static void _ncl_kright(struct line *l);
794 static void _ncl_krefresh(struct line *l);
795 static void _ncl_kbwddelw(struct line *l);
796 static void _ncl_kgow(struct line *l, ssize_t dir);
797 static void _ncl_kother(struct line *l, wchar_t wc);
798 # ifdef HAVE_HISTORY
799 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
800 static size_t _ncl_khist(struct line *l, bool_t backwd);
801 static size_t _ncl_krhist(struct line *l);
802 # endif
803 # ifdef HAVE_TABEXPAND
804 static size_t _ncl_kht(struct line *l);
805 # endif
806 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
807 size_t len SMALLOC_DEBUG_ARGS);
809 static void
810 _ncl_sigs_up(void)
812 NYD_ENTER;
813 if (_ncl_oint.sint == -1)
814 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
815 if (_ncl_oquit.sint == -1)
816 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
817 if (_ncl_oterm.sint == -1)
818 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
819 if (_ncl_ohup.sint == -1)
820 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
821 if (_ncl_otstp.sint == -1)
822 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
823 if (_ncl_ottin.sint == -1)
824 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
825 if (_ncl_ottou.sint == -1)
826 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
827 NYD_LEAVE;
830 static void
831 _ncl_sigs_down(void)
833 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
834 sighandler_type st;
835 NYD_ENTER;
837 if (_ncl_ottou.sint != -1) {
838 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
839 safe_signal(SIGTTOU, st);
841 if (_ncl_ottin.sint != -1) {
842 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
843 safe_signal(SIGTTIN, st);
845 if (_ncl_otstp.sint != -1) {
846 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
847 safe_signal(SIGTSTP, st);
849 if (_ncl_ohup.sint != -1) {
850 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
851 safe_signal(SIGHUP, st);
853 if (_ncl_oterm.sint != -1) {
854 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
855 safe_signal(SIGTERM, st);
857 if (_ncl_oquit.sint != -1) {
858 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
859 safe_signal(SIGQUIT, st);
861 if (_ncl_oint.sint != -1) {
862 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
863 safe_signal(SIGINT, st);
865 NYD_LEAVE;
868 static void
869 _ncl_term_mode(bool_t raw)
871 struct termios *tiosp = &_ncl_tios.told;
872 NYD_ENTER;
874 if (!raw)
875 goto jleave;
877 /* Always requery the attributes, in case we've been moved from background
878 * to foreground or however else in between sessions */
879 tcgetattr(STDIN_FILENO, tiosp);
880 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
881 tiosp = &_ncl_tios.tnew;
882 tiosp->c_cc[VMIN] = 1;
883 tiosp->c_cc[VTIME] = 0;
884 tiosp->c_iflag &= ~(ISTRIP);
885 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
886 jleave:
887 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
888 NYD_LEAVE;
891 static void
892 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
894 size_t i;
895 NYD_ENTER;
897 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
898 if (i > *l->x_bufsize) {
899 i <<= 1;
900 *l->x_bufsize = i;
901 l->line.cbuf =
902 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
904 NYD_LEAVE;
907 static void
908 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
910 size_t j;
911 NYD_ENTER;
913 if (i > 0)
914 memmove(cap, cap + 1, i * sizeof(*cap));
916 /* And.. the (rest of the) visual update */
917 for (j = 0; j < i; ++j)
918 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
919 fputs(" \b", stdout);
920 for (j = 0; j < i; ++j)
921 putchar('\b');
922 NYD_LEAVE;
925 static ssize_t
926 _ncl_wboundary(struct line *l, ssize_t dir)
928 size_t c, t, i;
929 struct cell *cap;
930 bool_t anynon;
931 NYD_ENTER;
933 c = l->cursor;
934 t = l->topins;
935 i = (size_t)-1;
937 if (dir < 0) {
938 if (c == 0)
939 goto jleave;
940 } else if (c == t)
941 goto jleave;
942 else
943 --t, --c; /* Unsigned wrapping may occur (twice), then */
945 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
946 wchar_t wc = cap[c + dir].wc;
947 if (iswblank(wc) || iswpunct(wc)) {
948 if (anynon)
949 break;
950 } else
951 anynon = TRU1;
952 ++i;
953 c += dir;
954 if (dir < 0) {
955 if (c == 0)
956 break;
957 } else if (c == t)
958 break;
960 jleave:
961 NYD_LEAVE;
962 return (ssize_t)i;
965 static ssize_t
966 _ncl_cell2dat(struct line *l)
968 size_t len = 0, i;
969 NYD_ENTER;
971 if (l->topins > 0)
972 for (i = 0; i < l->topins; ++i) {
973 struct cell *cap = l->line.cells + i;
974 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
975 len += cap->count;
977 l->line.cbuf[len] = '\0';
978 NYD_LEAVE;
979 return (ssize_t)len;
982 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
983 static void
984 _ncl_cell2save(struct line *l)
986 size_t len, i;
987 struct cell *cap;
988 NYD_ENTER;
990 l->savec.s = NULL, l->savec.l = 0;
991 if (l->topins == 0)
992 goto jleave;
994 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
995 len += cap->count;
997 l->savec.l = len;
998 l->savec.s = salloc(len + 1);
1000 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
1001 memcpy(l->savec.s + len, cap->cbuf, cap->count);
1002 len += cap->count;
1004 l->savec.s[len] = '\0';
1005 jleave:
1006 NYD_LEAVE;
1008 # endif
1010 static void
1011 _ncl_khome(struct line *l, bool_t dobell)
1013 size_t c = l->cursor;
1014 NYD_ENTER;
1016 if (c > 0) {
1017 l->cursor = 0;
1018 while (c-- != 0)
1019 putchar('\b');
1020 } else if (dobell)
1021 putchar('\a');
1022 NYD_LEAVE;
1025 static void
1026 _ncl_kend(struct line *l)
1028 ssize_t i;
1029 NYD_ENTER;
1031 i = (ssize_t)(l->topins - l->cursor);
1033 if (i > 0) {
1034 l->cursor = l->topins;
1035 while (i-- != 0)
1036 fputs(l->nd, stdout);
1037 } else
1038 putchar('\a');
1039 NYD_LEAVE;
1042 static void
1043 _ncl_kbs(struct line *l)
1045 ssize_t c, t;
1046 NYD_ENTER;
1048 c = l->cursor;
1049 t = l->topins;
1051 if (c > 0) {
1052 putchar('\b');
1053 l->cursor = --c;
1054 l->topins = --t;
1055 t -= c;
1056 _ncl_bs_eof_dvup(l->line.cells + c, t);
1057 } else
1058 putchar('\a');
1059 NYD_LEAVE;
1062 static void
1063 _ncl_kkill(struct line *l, bool_t dobell)
1065 size_t j, c, i;
1066 NYD_ENTER;
1068 c = l->cursor;
1069 i = (size_t)(l->topins - c);
1071 if (i > 0) {
1072 l->topins = c;
1073 for (j = i; j != 0; --j)
1074 putchar(' ');
1075 for (j = i; j != 0; --j)
1076 putchar('\b');
1077 } else if (dobell)
1078 putchar('\a');
1079 NYD_LEAVE;
1082 static ssize_t
1083 _ncl_keof(struct line *l)
1085 size_t c, t;
1086 ssize_t i;
1087 NYD_ENTER;
1089 c = l->cursor;
1090 t = l->topins;
1091 i = (ssize_t)(t - c);
1093 if (i > 0) {
1094 l->topins = --t;
1095 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1096 } else if (t == 0 && !ok_blook(ignoreeof)) {
1097 fputs("^D", stdout);
1098 fflush(stdout);
1099 i = -1;
1100 } else {
1101 putchar('\a');
1102 i = 0;
1104 NYD_LEAVE;
1105 return i;
1108 static void
1109 _ncl_kleft(struct line *l)
1111 NYD_ENTER;
1112 if (l->cursor > 0) {
1113 --l->cursor;
1114 putchar('\b');
1115 } else
1116 putchar('\a');
1117 NYD_LEAVE;
1120 static void
1121 _ncl_kright(struct line *l)
1123 NYD_ENTER;
1124 if (l->cursor < l->topins) {
1125 ++l->cursor;
1126 fputs(l->nd, stdout);
1127 } else
1128 putchar('\a');
1129 NYD_LEAVE;
1132 static void
1133 _ncl_krefresh(struct line *l)
1135 struct cell *cap;
1136 size_t i;
1137 NYD_ENTER;
1139 putchar('\r');
1140 if (l->prompt != NULL && *l->prompt != '\0')
1141 fputs(l->prompt, stdout);
1142 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1143 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1144 for (i = l->topins - l->cursor; i > 0; --i)
1145 putchar('\b');
1146 NYD_LEAVE;
1149 static void
1150 _ncl_kbwddelw(struct line *l)
1152 ssize_t i;
1153 size_t c, t, j;
1154 struct cell *cap;
1155 NYD_ENTER;
1157 i = _ncl_wboundary(l, -1);
1158 if (i <= 0) {
1159 if (i < 0)
1160 putchar('\a');
1161 goto jleave;
1164 c = l->cursor - i;
1165 t = l->topins;
1166 l->topins = t - i;
1167 l->cursor = c;
1168 cap = l->line.cells + c;
1170 if (t != l->cursor) {
1171 j = t - c + i;
1172 memmove(cap, cap + i, j * sizeof(*cap));
1175 for (j = i; j > 0; --j)
1176 putchar('\b');
1177 for (j = l->topins - c; j > 0; ++cap, --j)
1178 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1179 for (j = i; j > 0; --j)
1180 putchar(' ');
1181 for (j = t - c; j > 0; --j)
1182 putchar('\b');
1183 jleave:
1184 NYD_LEAVE;
1187 static void
1188 _ncl_kgow(struct line *l, ssize_t dir)
1190 ssize_t i;
1191 NYD_ENTER;
1193 i = _ncl_wboundary(l, dir);
1194 if (i <= 0) {
1195 if (i < 0)
1196 putchar('\a');
1197 goto jleave;
1200 if (dir < 0) {
1201 l->cursor -= i;
1202 while (i-- > 0)
1203 putchar('\b');
1204 } else {
1205 l->cursor += i;
1206 while (i-- > 0)
1207 fputs(l->nd, stdout);
1209 jleave:
1210 NYD_LEAVE;
1213 static void
1214 _ncl_kother(struct line *l, wchar_t wc)
1216 /* Append if at EOL, insert otherwise;
1217 * since we may move around character-wise, always use a fresh ps */
1218 mbstate_t ps;
1219 struct cell cell, *cap;
1220 size_t i, c;
1221 NYD_ENTER;
1223 /* First init a cell and see wether we'll really handle this wc */
1224 cell.wc = wc;
1225 memset(&ps, 0, sizeof ps);
1226 i = wcrtomb(cell.cbuf, wc, &ps);
1227 if (i > MB_LEN_MAX)
1228 goto jleave;
1229 cell.count = (ui_it)i;
1230 if (enc_has_state) {
1231 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1232 if (i == 1)
1234 else if (--i < MB_LEN_MAX)
1235 cell.count += (ui_it)i;
1236 else
1237 goto jleave;
1240 /* Yes, we will! Place it in the array */
1241 c = l->cursor++;
1242 i = l->topins++ - c;
1243 cap = l->line.cells + c;
1244 if (i > 0)
1245 memmove(cap + 1, cap, i * sizeof(cell));
1246 memcpy(cap, &cell, sizeof cell);
1248 /* And update visual */
1249 c = i;
1251 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1252 while ((++cap, i-- != 0));
1253 while (c-- != 0)
1254 putchar('\b');
1255 jleave:
1256 NYD_LEAVE;
1259 # ifdef HAVE_HISTORY
1260 static size_t
1261 __ncl_khist_shared(struct line *l, struct hist *hp)
1263 size_t rv;
1264 NYD_ENTER;
1266 if ((l->hist = hp) != NULL) {
1267 l->defc.s = savestrbuf(hp->dat, hp->len);
1268 rv =
1269 l->defc.l = hp->len;
1270 if (l->topins > 0) {
1271 _ncl_khome(l, FAL0);
1272 _ncl_kkill(l, FAL0);
1274 } else {
1275 putchar('\a');
1276 rv = 0;
1278 NYD_LEAVE;
1279 return rv;
1282 static size_t
1283 _ncl_khist(struct line *l, bool_t backwd)
1285 struct hist *hp;
1286 size_t rv;
1287 NYD_ENTER;
1289 /* If we're not in history mode yet, save line content;
1290 * also, disallow forward search, then, and, of course, bail unless we
1291 * do have any history at all */
1292 if ((hp = l->hist) == NULL) {
1293 if (!backwd)
1294 goto jleave;
1295 if ((hp = _ncl_hist) == NULL)
1296 goto jleave;
1297 _ncl_cell2save(l);
1298 goto jleave;
1301 hp = backwd ? hp->older : hp->younger;
1302 jleave:
1303 rv = __ncl_khist_shared(l, hp);
1304 NYD_LEAVE;
1305 return rv;
1308 static size_t
1309 _ncl_krhist(struct line *l)
1311 struct str orig_savec;
1312 struct hist *hp = NULL;
1313 size_t rv;
1314 NYD_ENTER;
1316 /* We cannot complete an empty line */
1317 if (l->topins == 0) {
1318 /* XXX The upcoming hard reset would restore a set savec buffer,
1319 * XXX so forcefully reset that. A cleaner solution would be to
1320 * XXX reset it whenever a restore is no longer desired */
1321 l->savec.s = NULL, l->savec.l = 0;
1322 goto jleave;
1324 if ((hp = l->hist) == NULL) {
1325 if ((hp = _ncl_hist) == NULL)
1326 goto jleave;
1327 orig_savec.s = NULL;
1328 orig_savec.l = 0; /* silence CC */
1329 } else if ((hp = hp->older) == NULL)
1330 goto jleave;
1331 else
1332 orig_savec = l->savec;
1334 if (orig_savec.s == NULL)
1335 _ncl_cell2save(l);
1336 for (; hp != NULL; hp = hp->older)
1337 if (is_prefix(l->savec.s, hp->dat))
1338 break;
1339 if (orig_savec.s != NULL)
1340 l->savec = orig_savec;
1341 jleave:
1342 rv = __ncl_khist_shared(l, hp);
1343 NYD_LEAVE;
1344 return rv;
1346 # endif
1348 # ifdef HAVE_TABEXPAND
1349 static size_t
1350 _ncl_kht(struct line *l)
1352 struct str orig, bot, topp, sub, exp;
1353 struct cell *cword, *ctop, *cx;
1354 bool_t set_savec = FAL0;
1355 size_t rv = 0;
1356 NYD_ENTER;
1358 /* We cannot expand an empty line */
1359 if (l->topins == 0)
1360 goto jleave;
1362 /* Get plain line data; if this is the first expansion/xy, update the
1363 * very original content so that ^G gets the origin back */
1364 orig = l->savec;
1365 _ncl_cell2save(l);
1366 exp = l->savec;
1367 if (orig.s != NULL)
1368 l->savec = orig;
1369 else
1370 set_savec = TRU1;
1371 orig = exp;
1373 cword = l->line.cells;
1374 ctop = cword + l->cursor;
1376 /* topp: separate data right of cursor */
1377 if ((cx = cword + l->topins) != ctop) {
1378 for (rv = 0; cx > ctop; --cx)
1379 rv += cx->count;
1380 topp.l = rv;
1381 topp.s = orig.s + orig.l - rv;
1382 } else
1383 topp.s = NULL, topp.l = 0;
1385 /* bot, sub: we cannot expand the entire data left of cursor, but only
1386 * the last "word", so separate them */
1387 while (cx > cword && !iswspace(cx[-1].wc))
1388 --cx;
1389 for (rv = 0; cword < cx; ++cword)
1390 rv += cword->count;
1391 sub =
1392 bot = orig;
1393 bot.l = rv;
1394 sub.s += rv;
1395 sub.l -= rv;
1396 sub.l -= topp.l;
1398 /* Leave room for "implicit asterisk" expansion, as below */
1399 if (sub.l == 0) {
1400 sub.s = UNCONST("*");
1401 sub.l = 1;
1402 } else {
1403 exp.s = salloc(sub.l + 1 +1);
1404 memcpy(exp.s, sub.s, sub.l);
1405 exp.s[sub.l] = '\0';
1406 sub.s = exp.s;
1409 /* TODO there is a TODO note upon fexpand() with multi-return;
1410 * TODO if that will change, the if() below can be simplified */
1411 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1412 jredo:
1413 hold_all_sigs();
1414 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1415 rele_all_sigs();
1417 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1418 goto jnope;
1419 /* If the expansion equals the original string, assume the user wants what
1420 * is usually known as tab completion, append `*' and restart */
1421 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1422 if (sub.s[sub.l - 1] == '*')
1423 goto jnope;
1424 sub.s[sub.l++] = '*';
1425 sub.s[sub.l] = '\0';
1426 goto jredo;
1429 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1430 * Take care to take *prompt* into account, since we don't know
1431 * anything about it's visual length (fputs(3) is used), simply
1432 * assume each character requires two columns */
1433 /* TODO the problem is that we loose control otherwise; in the best
1434 * TODO case the user can control via ^A and ^K etc., but be safe;
1435 * TODO we cannot simply adjust fexpand() because we don't know how
1436 * TODO that is implemented... The real solution would be to check
1437 * TODO wether we fit on a line, and start a pager if not.
1438 * TODO However, that should be part of a real tab-COMPLETION, then,
1439 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1440 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1441 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1442 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1443 char const e1[] = "[maximum line size exceeded]";
1444 exp.s = UNCONST(e1);
1445 exp.l = sizeof(e1) - 1;
1446 topp.l = 0;
1447 if (rv + bot.l + exp.l >= MAX_INPUT)
1448 bot.l = 0;
1449 if (rv + exp.l >= MAX_INPUT) {
1450 char const e2[] = "[ERR]";
1451 exp.s = UNCONST(e2);
1452 exp.l = sizeof(e2) - 1;
1456 orig.l = bot.l + exp.l + topp.l;
1457 orig.s = salloc(orig.l + 1 + 5);
1458 if ((rv = bot.l) > 0)
1459 memcpy(orig.s, bot.s, rv);
1460 memcpy(orig.s + rv, exp.s, exp.l);
1461 rv += exp.l;
1462 if (topp.l > 0) {
1463 memcpy(orig.s + rv, topp.s, topp.l);
1464 rv += topp.l;
1466 orig.s[rv] = '\0';
1468 l->defc = orig;
1469 _ncl_khome(l, FAL0);
1470 _ncl_kkill(l, FAL0);
1471 jleave:
1472 NYD_LEAVE;
1473 return rv;
1474 jnope:
1475 /* If we've provided a default content, but failed to expand, there is
1476 * nothing we can "revert to": drop that default again */
1477 if (set_savec)
1478 l->savec.s = NULL, l->savec.l = 0;
1479 rv = 0;
1480 goto jleave;
1482 # endif /* HAVE_TABEXPAND */
1484 static ssize_t
1485 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1486 SMALLOC_DEBUG_ARGS)
1488 /* We want to save code, yet we may have to incorporate a lines'
1489 * default content and / or default input to switch back to after some
1490 * history movement; let "len > 0" mean "have to display some data
1491 * buffer", and only otherwise read(2) it */
1492 mbstate_t ps[2];
1493 struct line l;
1494 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
1495 wchar_t wc;
1496 ssize_t rv;
1497 ui32_t maybe_cursor;
1498 NYD_ENTER;
1500 memset(&l, 0, sizeof l);
1501 l.line.cbuf = *buf;
1502 if (len != 0) {
1503 l.defc.s = savestrbuf(*buf, len);
1504 l.defc.l = len;
1506 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1507 l.prompt = prompt = "?ERR?";
1508 /* TODO *l.nd=='\0' only because we have no value-cache -> see acmava.c */
1509 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1510 l.nd = "\033[C"; /* XXX no "magic" constant */
1511 l.x_buf = buf;
1512 l.x_bufsize = bufsize;
1514 if (prompt != NULL && *prompt != '\0') {
1515 fputs(prompt, stdout);
1516 fflush(stdout);
1518 jrestart:
1519 memset(ps, 0, sizeof ps);
1520 maybe_cursor = 0;
1521 /* TODO: NCL: we should output the reset sequence when we jrestart:
1522 * TODO: NCL: if we are using a stateful encoding? !
1523 * TODO: NCL: in short: this is not yet well understood */
1524 for (;;) {
1525 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1527 /* Normal read(2)? Else buffer-takeover: speed this one up */
1528 if (len == 0)
1529 cbufp =
1530 cbuf = cbuf_base;
1531 else {
1532 assert(l.defc.l > 0 && l.defc.s != NULL);
1533 cbufp =
1534 cbuf = l.defc.s + (l.defc.l - len);
1535 cbufp += len;
1538 /* Read in the next complete multibyte character */
1539 for (;;) {
1540 if (len == 0) {
1541 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1542 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1543 continue;
1544 goto jleave;
1546 ++cbufp;
1549 /* Ach! the ISO C multibyte handling!
1550 * Encodings with locking shift states cannot really be helped, since
1551 * it is impossible to only query the shift state, as opposed to the
1552 * entire shift state + character pair (via ISO C functions) */
1553 rv = (ssize_t)mbrtowc(&wc, cbuf, (size_t)(cbufp - cbuf), ps + 0);
1554 if (rv <= 0) {
1555 /* Any error during take-over can only result in a hard reset;
1556 * Otherwise, if it's a hard error, or if too many redundant shift
1557 * sequences overflow our buffer, also perform a hard reset */
1558 if (len != 0 || rv == -1 ||
1559 sizeof cbuf_base == (size_t)(cbufp - cbuf)) {
1560 l.savec.s = l.defc.s = NULL,
1561 l.savec.l = l.defc.l = len = 0;
1562 putchar('\a');
1563 wc = 'G';
1564 goto jreset;
1566 /* Otherwise, due to the way we deal with the buffer, we need to
1567 * restore the mbstate_t from before this conversion */
1568 ps[0] = ps[1];
1569 continue;
1572 if (len != 0 && (len -= (size_t)rv) == 0)
1573 l.defc.s = NULL, l.defc.l = 0;
1574 ps[1] = ps[0];
1575 break;
1578 /* Don't interpret control bytes during buffer take-over */
1579 if (cbuf != cbuf_base)
1580 goto jprint;
1581 switch (wc) {
1582 case 'A' ^ 0x40: /* cursor home */
1583 _ncl_khome(&l, TRU1);
1584 break;
1585 j_b:
1586 case 'B' ^ 0x40: /* backward character */
1587 _ncl_kleft(&l);
1588 break;
1589 /* 'C': interrupt (CTRL-C) */
1590 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1591 if ((rv = _ncl_keof(&l)) < 0)
1592 goto jleave;
1593 break;
1594 case 'E' ^ 0x40: /* end of line */
1595 _ncl_kend(&l);
1596 break;
1597 j_f:
1598 case 'F' ^ 0x40: /* forward character */
1599 _ncl_kright(&l);
1600 break;
1601 /* 'G' below */
1602 case 'H' ^ 0x40: /* backspace */
1603 case '\177':
1604 _ncl_kbs(&l);
1605 break;
1606 case 'I' ^ 0x40: /* horizontal tab */
1607 # ifdef HAVE_TABEXPAND
1608 if ((len = _ncl_kht(&l)) > 0)
1609 goto jrestart;
1610 # endif
1611 goto jbell;
1612 case 'J' ^ 0x40: /* NL (\n) */
1613 goto jdone;
1614 case 'G' ^ 0x40: /* full reset */
1615 jreset:
1616 /* FALLTHRU */
1617 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1618 _ncl_khome(&l, FAL0);
1619 /* FALLTHRU */
1620 case 'K' ^ 0x40: /* kill from cursor to end of line */
1621 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1622 /* (Handle full reset?) */
1623 if (wc == ('G' ^ 0x40)) {
1624 # ifdef HAVE_HISTORY
1625 l.hist = NULL;
1626 # endif
1627 if ((len = l.savec.l) != 0) {
1628 l.defc = l.savec;
1629 l.savec.s = NULL, l.savec.l = 0;
1630 } else
1631 len = l.defc.l;
1633 fflush(stdout);
1634 goto jrestart;
1635 case 'L' ^ 0x40: /* repaint line */
1636 _ncl_krefresh(&l);
1637 break;
1638 /* 'M': CR (\r) */
1639 j_n:
1640 case 'N' ^ 0x40: /* history next */
1641 # ifdef HAVE_HISTORY
1642 if (l.hist == NULL)
1643 goto jbell;
1644 if ((len = _ncl_khist(&l, FAL0)) > 0)
1645 goto jrestart;
1646 wc = 'G' ^ 0x40;
1647 goto jreset;
1648 # else
1649 goto jbell;
1650 # endif
1651 /* 'O' */
1652 j_p:
1653 case 'P' ^ 0x40: /* history previous */
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 (maybe_cursor++ != 0)
1687 goto jreset;
1688 continue;
1689 default:
1690 /* XXX Handle usual ^[[[ABCD] cursor keys -- UGLY, "MAGIC", INFLEX */
1691 if (maybe_cursor > 0) {
1692 if (++maybe_cursor == 2) {
1693 if (wc == L'[')
1694 continue;
1695 maybe_cursor = 0;
1696 } else {
1697 maybe_cursor = 0;
1698 switch (wc) {
1699 case L'A': goto j_p;
1700 case L'B': goto j_n;
1701 case L'C': goto j_f;
1702 case L'D': goto j_b;
1704 _ncl_kother(&l, L'[');
1707 jprint:
1708 if (iswprint(wc)) {
1709 _ncl_kother(&l, wc);
1710 /* Don't clear the history during takeover..
1711 * ..and also avoid fflush()ing unless we've
1712 * worked the entire buffer */
1713 if (len > 0)
1714 continue;
1715 # ifdef HAVE_HISTORY
1716 if (cbuf == cbuf_base)
1717 l.hist = NULL;
1718 # endif
1719 } else {
1720 jbell:
1721 putchar('\a');
1723 break;
1725 fflush(stdout);
1728 /* We have a completed input line, convert the struct cell data to its
1729 * plain character equivalent */
1730 jdone:
1731 putchar('\n');
1732 fflush(stdout);
1733 len = _ncl_cell2dat(&l);
1734 rv = (ssize_t)len;
1735 jleave:
1736 NYD_LEAVE;
1737 return rv;
1740 FL void
1741 tty_init(void)
1743 # ifdef HAVE_HISTORY
1744 long hs;
1745 char *v, *lbuf;
1746 FILE *f;
1747 size_t lsize, cnt, llen;
1748 # endif
1749 NYD_ENTER;
1751 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1752 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1753 _ncl_ottou.sint = -1;
1755 # ifdef HAVE_HISTORY
1756 _CL_HISTSIZE(hs);
1757 _ncl_hist_size = 0;
1758 _ncl_hist_size_max = hs;
1759 if (hs == 0)
1760 goto jleave;
1762 _CL_HISTFILE(v);
1763 if (v == NULL)
1764 goto jleave;
1766 hold_all_sigs(); /* XXX too heavy, yet we may jump even here!? */
1767 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1768 if (f == NULL)
1769 goto jdone;
1771 lbuf = NULL;
1772 lsize = 0;
1773 cnt = fsize(f);
1774 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1775 if (llen > 0 && lbuf[llen - 1] == '\n')
1776 lbuf[--llen] = '\0';
1777 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1778 continue;
1779 _ncl_hist_load = TRU1;
1780 tty_addhist(lbuf);
1781 _ncl_hist_load = FAL0;
1783 if (lbuf != NULL)
1784 free(lbuf);
1786 fclose(f);
1787 jdone:
1788 rele_all_sigs(); /* XXX remove jumps */
1789 jleave:
1790 # endif /* HAVE_HISTORY */
1791 NYD_LEAVE;
1794 FL void
1795 tty_destroy(void)
1797 # ifdef HAVE_HISTORY
1798 long hs;
1799 char *v;
1800 struct hist *hp;
1801 FILE *f;
1802 # endif
1803 NYD_ENTER;
1805 # ifdef HAVE_HISTORY
1806 _CL_HISTSIZE(hs);
1807 if (hs == 0)
1808 goto jleave;
1810 _CL_HISTFILE(v);
1811 if (v == NULL)
1812 goto jleave;
1814 if ((hp = _ncl_hist) != NULL)
1815 while (hp->older != NULL && hs-- != 0)
1816 hp = hp->older;
1818 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1819 f = fopen(v, "w"); /* TODO temporary + rename?! */
1820 if (f == NULL)
1821 goto jdone;
1822 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1823 goto jclose;
1825 for (; hp != NULL; hp = hp->younger) {
1826 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1827 putc('\n', f);
1829 jclose:
1830 fclose(f);
1831 jdone:
1832 rele_all_sigs(); /* XXX remove jumps */
1833 jleave:
1834 # endif /* HAVE_HISTORY */
1835 NYD_LEAVE;
1838 FL void
1839 tty_signal(int sig)
1841 sigset_t nset, oset;
1842 NYD_X; /* Signal handler */
1844 switch (sig) {
1845 case SIGWINCH:
1846 /* We don't deal with SIGWINCH, yet get called from main.c */
1847 break;
1848 default:
1849 _ncl_term_mode(FAL0);
1850 _ncl_sigs_down();
1851 sigemptyset(&nset);
1852 sigaddset(&nset, sig);
1853 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1854 kill(0, sig);
1855 /* When we come here we'll continue editing, so reestablish */
1856 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1857 _ncl_sigs_up();
1858 _ncl_term_mode(TRU1);
1859 break;
1863 FL int
1864 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1865 SMALLOC_DEBUG_ARGS)
1867 ssize_t nn;
1868 NYD_ENTER;
1870 /* Of course we have races here, but they cannot be avoided on POSIX
1871 * (except by even *more* actions) */
1872 _ncl_sigs_up();
1873 _ncl_term_mode(TRU1);
1874 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1875 _ncl_term_mode(FAL0);
1876 _ncl_sigs_down();
1877 NYD_LEAVE;
1878 return (int)nn;
1881 FL void
1882 tty_addhist(char const *s)
1884 # ifdef HAVE_HISTORY
1885 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1886 size_t l = strlen(s);
1887 struct hist *h, *o, *y;
1888 # endif
1889 NYD_ENTER;
1890 UNUSED(s);
1892 # ifdef HAVE_HISTORY
1893 if (_ncl_hist_size_max == 0)
1894 goto j_leave;
1895 _CL_CHECK_ADDHIST(s, goto j_leave);
1897 /* Eliminating duplicates is expensive, but simply inacceptable so
1898 * during the load of a potentially large history file! */
1899 if (!_ncl_hist_load)
1900 for (h = _ncl_hist; h != NULL; h = h->older)
1901 if (h->len == l && !strcmp(h->dat, s)) {
1902 hold_all_sigs(); /* TODO */
1903 o = h->older;
1904 y = h->younger;
1905 if (o != NULL)
1906 o->younger = y;
1907 else
1908 _ncl_hist_tail = y;
1909 if (y != NULL)
1910 y->older = o;
1911 else
1912 _ncl_hist = o;
1913 goto jleave;
1915 hold_all_sigs();
1917 ++_ncl_hist_size;
1918 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1919 --_ncl_hist_size;
1920 if ((h = _ncl_hist_tail) != NULL) {
1921 if ((_ncl_hist_tail = h->younger) == NULL)
1922 _ncl_hist = NULL;
1923 else
1924 _ncl_hist_tail->older = NULL;
1925 free(h);
1929 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1930 h->len = l;
1931 memcpy(h->dat, s, l +1);
1932 jleave:
1933 if ((h->older = _ncl_hist) != NULL)
1934 _ncl_hist->younger = h;
1935 else
1936 _ncl_hist_tail = h;
1937 h->younger = NULL;
1938 _ncl_hist = h;
1940 rele_all_sigs();
1941 j_leave:
1942 # endif
1943 NYD_LEAVE;
1946 # ifdef HAVE_HISTORY
1947 FL int
1948 c_history(void *v)
1950 C_HISTORY_SHARED;
1952 jlist: {
1953 FILE *fp;
1954 size_t i, b;
1955 struct hist *h;
1957 if (_ncl_hist == NULL)
1958 goto jleave;
1960 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
1961 NULL) {
1962 perror("tmpfile");
1963 v = NULL;
1964 goto jleave;
1967 i = _ncl_hist_size;
1968 b = 0;
1969 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
1970 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
1971 (ul_it)i, h->dat, (ul_it)b, (ul_it)h->len);
1973 page_or_print(fp, i);
1974 Fclose(fp);
1976 goto jleave;
1978 jclear: {
1979 struct hist *h;
1980 while ((h = _ncl_hist) != NULL) {
1981 _ncl_hist = h->older;
1982 free(h);
1984 _ncl_hist_tail = NULL;
1985 _ncl_hist_size = 0;
1987 goto jleave;
1989 jentry: {
1990 struct hist *h = _ncl_hist;
1991 if (UICMP(z, entry, <=, _ncl_hist_size)) {
1992 entry = (long)_ncl_hist_size - entry;
1993 for (h = _ncl_hist;; h = h->older)
1994 if (h == NULL)
1995 break;
1996 else if (entry-- != 0)
1997 continue;
1998 else {
1999 v = temporary_arg_v_store = h->dat;
2000 goto jleave;
2003 v = NULL;
2005 goto jleave;
2007 # endif /* HAVE_HISTORY */
2008 #endif /* HAVE_NCL */
2011 * The really-nothing-at-all implementation
2014 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2015 FL void
2016 tty_init(void)
2018 NYD_ENTER;
2019 NYD_LEAVE;
2022 FL void
2023 tty_destroy(void)
2025 NYD_ENTER;
2026 NYD_LEAVE;
2029 FL void
2030 tty_signal(int sig)
2032 NYD_X; /* Signal handler */
2033 UNUSED(sig);
2036 FL int
2037 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2038 SMALLOC_DEBUG_ARGS)
2040 /* TODO The nothing-at-all tty layer even forces re-entering all the
2041 * TODO original data when re-editing a field */
2042 bool_t doffl = FAL0;
2043 int rv;
2044 NYD_ENTER;
2046 if (prompt != NULL && *prompt != '\0') {
2047 fputs(prompt, stdout);
2048 doffl = TRU1;
2050 if (n > 0) {
2051 fprintf(stdout, tr(511, "{former content: %.*s} "), (int)n, *linebuf);
2052 n = 0;
2053 doffl = TRU1;
2055 if (doffl)
2056 fflush(stdout);
2057 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2058 NYD_LEAVE;
2059 return rv;
2062 FL void
2063 tty_addhist(char const *s)
2065 NYD_ENTER;
2066 UNUSED(s);
2067 NYD_LEAVE;
2069 #endif /* nothing at all */
2071 /* vim:set fenc=utf-8:s-it-mode */