NYD: quit.c
[s-mailx.git] / tty.c
blobb7b4152290961ff5e5c751bd71f30666c6a6b4e3
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 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
39 /* The NCL version is
41 * Copyright (c) 2013 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
43 * Permission to use, copy, modify, and/or distribute this software for any
44 * purpose with or without fee is hereby granted, provided that the above
45 * copyright notice and this permission notice appear in all copies.
47 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
48 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
49 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
50 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
51 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
52 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
53 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
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;\
105 if (*argv == NULL)\
106 goto jlist;\
107 if (argv[1] != NULL)\
108 goto jerr;\
109 if (asccasecmp(*argv, "show") == 0)\
110 goto jlist;\
111 if (asccasecmp(*argv, "clear") == 0)\
112 goto jclear;\
113 if ((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
114 goto jentry;\
115 jerr:\
116 fprintf(stderr, "Synopsis: history: %s\n", tr(431,\
117 "<show> (default), <clear> or select <NO> from editor history"));\
118 v = NULL;\
119 jleave:\
120 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
121 #endif /* HAVE_HISTORY */
123 /* fexpand() flags for expand-on-tab */
124 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
127 * Because we have multiple identical implementations, change file layout a bit
128 * and place the implementations one after the other below the other externals
131 FL bool_t
132 getapproval(char const *prompt, bool_t noninteract_default)
134 bool_t rv;
136 if (!(options & OPT_INTERACTIVE)) {
137 rv = noninteract_default;
138 goto jleave;
141 if (prompt == NULL)
142 prompt = tr(264, "Continue (y/n)? ");
144 rv = FAL0;
145 if (readline_input(prompt, FAL0, &termios_state.ts_linebuf,
146 &termios_state.ts_linesize, NULL) >= 0)
147 switch (termios_state.ts_linebuf[0]) {
148 case 'y':
149 case 'Y':
150 rv = TRU1;
151 /* FALLTHRU */
152 default:
153 break;
155 termios_state_reset();
156 jleave:
157 return rv;
160 FL bool_t
161 yorn(char const *msg)
163 bool_t rv;
165 rv = getapproval(msg, TRU1);
166 return rv;
169 FL char *
170 getuser(char const *query)
172 char *user = NULL;
174 if (query == NULL)
175 query = tr(509, "User: ");
177 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
178 &termios_state.ts_linesize, NULL) >= 0)
179 user = termios_state.ts_linebuf;
180 termios_state_reset();
181 return user;
184 FL char *
185 getpassword(char const *query) /* FIXME encaps ttystate signal safe */
187 struct termios tios;
188 char *pass = NULL;
190 if (query == NULL)
191 query = tr(510, "Password: ");
192 fputs(query, stdout);
193 fflush(stdout);
196 * FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
197 * foreground pgrp, and can fail with EINTR!!
199 if (options & OPT_TTYIN) {
200 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
201 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
202 termios_state.ts_needs_reset = TRU1;
203 tios.c_iflag &= ~(ISTRIP);
204 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
205 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
208 if (readline_restart(stdin, &termios_state.ts_linebuf,
209 &termios_state.ts_linesize, 0) >= 0)
210 pass = termios_state.ts_linebuf;
211 termios_state_reset();
213 if (options & OPT_TTYIN)
214 fputc('\n', stdout);
215 return pass;
218 FL bool_t
219 getcredentials(char **user, char **pass)
221 bool_t rv = TRU1;
222 char *u = *user, *p = *pass;
224 if (u == NULL) {
225 if ((u = getuser(NULL)) == NULL)
226 rv = FAL0;
227 else if (p == NULL)
228 u = savestr(u);
229 *user = u;
232 if (p == NULL) {
233 if ((p = getpassword(NULL)) == NULL)
234 rv = FAL0;
235 *pass = p;
237 return rv;
241 * readline(3)
244 #ifdef HAVE_READLINE
245 static sighandler_type _rl_shup;
246 static char * _rl_buf; /* pre_input() hook: initial line */
247 static int _rl_buflen; /* content, and its length */
249 static int _rl_pre_input(void);
251 static int
252 _rl_pre_input(void)
254 /* Handle leftover data from \ escaped former line */
255 rl_extend_line_buffer(_rl_buflen + 10);
256 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
257 rl_point = rl_end = _rl_buflen;
258 rl_pre_input_hook = (rl_hook_func_t*)NULL;
259 rl_redisplay();
260 return 0;
263 FL void
264 tty_init(void)
266 # ifdef HAVE_HISTORY
267 long hs;
268 char *v;
269 # endif
271 rl_readline_name = UNCONST(uagent);
272 # ifdef HAVE_HISTORY
273 _CL_HISTSIZE(hs);
274 using_history();
275 stifle_history((int)hs);
276 # endif
277 rl_read_init_file(NULL);
279 /* Because rl_read_init_file() may have introduced yet a different
280 * history size limit, simply load and incorporate the history, leave
281 * it up to readline(3) to do the rest */
282 # ifdef HAVE_HISTORY
283 _CL_HISTFILE(v);
284 if (v != NULL)
285 read_history(v);
286 # endif
289 FL void
290 tty_destroy(void)
292 # ifdef HAVE_HISTORY
293 char *v;
295 _CL_HISTFILE(v);
296 if (v != NULL)
297 write_history(v);
298 # endif
302 FL void
303 tty_signal(int sig)
305 sigset_t nset, oset;
307 switch (sig) {
308 # ifdef SIGWINCH
309 case SIGWINCH:
310 break;
311 # endif
312 case SIGHUP:
313 /* readline(3) doesn't catch it :( */
314 rl_free_line_state();
315 rl_cleanup_after_signal();
316 safe_signal(SIGHUP, _rl_shup);
317 sigemptyset(&nset);
318 sigaddset(&nset, sig);
319 sigprocmask(SIG_UNBLOCK, &nset, &oset);
320 kill(0, sig);
321 /* XXX When we come here we'll continue editing, so reestablish
322 * XXX cannot happen */
323 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
324 _rl_shup = safe_signal(SIGHUP, &tty_signal);
325 rl_reset_after_signal();
326 break;
327 default:
328 break;
332 FL int
333 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
334 SMALLOC_DEBUG_ARGS)
336 int nn;
337 char *line;
339 if (n > 0) {
340 _rl_buf = *linebuf;
341 _rl_buflen = (int)n;
342 rl_pre_input_hook = &_rl_pre_input;
345 _rl_shup = safe_signal(SIGHUP, &tty_signal);
346 line = readline(prompt != NULL ? prompt : "");
347 safe_signal(SIGHUP, _rl_shup);
349 if (line == NULL) {
350 nn = -1;
351 goto jleave;
353 n = strlen(line);
355 if (n >= *linesize) {
356 *linesize = LINESIZE + n + 1;
357 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
359 memcpy(*linebuf, line, n);
360 (free)(line);
361 (*linebuf)[n] = '\0';
362 nn = (int)n;
363 jleave:
364 return nn;
367 FL void
368 tty_addhist(char const *s)
370 # ifdef HAVE_HISTORY
371 _CL_CHECK_ADDHIST(s, goto jleave);
372 hold_all_sigs(); /* XXX too heavy */
373 add_history(s); /* XXX yet we jump away! */
374 rele_all_sigs(); /* XXX remove jumps */
375 jleave:
376 # endif
377 UNUSED(s);
380 # ifdef HAVE_HISTORY
381 FL int
382 c_history(void *v)
384 C_HISTORY_SHARED;
386 jlist: {
387 FILE *fp;
388 char *cp;
389 HISTORY_STATE *hs;
390 HIST_ENTRY **hl;
391 ul_it i, b;
393 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
394 perror("tmpfile");
395 v = NULL;
396 goto jleave;
398 rm(cp);
399 Ftfree(&cp);
401 hs = history_get_history_state();
403 for (i = (ul_it)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
404 size_t sl = strlen(cp = (*--hl)->line);
405 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n", i, cp, b, sl);
406 b += sl;
409 page_or_print(fp, (size_t)hs->length);
410 Fclose(fp);
412 goto jleave;
414 jclear:
415 clear_history();
416 goto jleave;
418 jentry: {
419 HISTORY_STATE *hs = history_get_history_state();
421 if (UICMP(z, entry, <=, hs->length))
422 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
423 else
424 v = NULL;
426 goto jleave;
428 # endif /* HAVE_HISTORY */
429 #endif /* HAVE_READLINE */
432 * BSD editline(3)
435 #ifdef HAVE_EDITLINE
436 static EditLine * _el_el; /* editline(3) handle */
437 static char const * _el_prompt; /* Current prompt */
438 # ifdef HAVE_HISTORY
439 static History * _el_hcom; /* History handle for commline */
440 # endif
442 static char const * _el_getprompt(void);
444 static char const *
445 _el_getprompt(void)
447 return _el_prompt;
450 FL void
451 tty_init(void)
453 # ifdef HAVE_HISTORY
454 HistEvent he;
455 long hs;
456 char *v;
457 # endif
459 # ifdef HAVE_HISTORY
460 _CL_HISTSIZE(hs);
461 _el_hcom = history_init();
462 history(_el_hcom, &he, H_SETSIZE, (int)hs);
463 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
464 # endif
466 _el_el = el_init(uagent, stdin, stdout, stderr);
467 el_set(_el_el, EL_SIGNAL, 1);
468 el_set(_el_el, EL_TERMINAL, NULL);
469 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
470 # ifdef HAVE_HISTORY
471 el_set(_el_el, EL_HIST, &history, _el_hcom);
472 # endif
473 el_set(_el_el, EL_EDITOR, "emacs");
474 # ifdef EL_PROMPT_ESC
475 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
476 # else
477 el_set(_el_el, EL_PROMPT, &_el_getprompt);
478 # endif
479 # if 0
480 el_set(_el_el, EL_ADDFN, "tab_complete",
481 "editline(3) internal completion function", &_el_file_cpl);
482 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
483 # endif
484 # ifdef HAVE_HISTORY
485 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
486 # endif
487 el_source(_el_el, NULL); /* Source ~/.editrc */
489 /* Because el_source() may have introduced yet a different history size
490 * limit, simply load and incorporate the history, leave it up to
491 * editline(3) to do the rest */
492 # ifdef HAVE_HISTORY
493 _CL_HISTFILE(v);
494 if (v != NULL)
495 history(_el_hcom, &he, H_LOAD, v);
496 # endif
499 FL void
500 tty_destroy(void)
502 # ifdef HAVE_HISTORY
503 HistEvent he;
504 char *v;
505 # endif
507 el_end(_el_el);
509 # ifdef HAVE_HISTORY
510 _CL_HISTFILE(v);
511 if (v != NULL)
512 history(_el_hcom, &he, H_SAVE, v);
513 history_end(_el_hcom);
514 # endif
517 FL void
518 tty_signal(int sig)
520 switch (sig) {
521 # ifdef SIGWINCH
522 case SIGWINCH:
523 el_resize(_el_el);
524 break;
525 # endif
526 default:
527 break;
531 FL int
532 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
533 SMALLOC_DEBUG_ARGS)
535 int nn;
536 char const *line;
538 _el_prompt = (prompt != NULL) ? prompt : "";
539 if (n > 0)
540 el_push(_el_el, *linebuf);
541 line = el_gets(_el_el, &nn);
543 if (line == NULL) {
544 nn = -1;
545 goto jleave;
547 assert(nn >= 0);
548 n = (size_t)nn;
549 if (n > 0 && line[n - 1] == '\n')
550 nn = (int)--n;
552 if (n >= *linesize) {
553 *linesize = LINESIZE + n + 1;
554 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
556 memcpy(*linebuf, line, n);
557 (*linebuf)[n] = '\0';
558 jleave:
559 return nn;
562 FL void
563 tty_addhist(char const *s)
565 # ifdef HAVE_HISTORY
566 /* Enlarge meaning of unique .. to something that rocks;
567 * xxx unfortunately this is expensive to do with editline(3)
568 * xxx maybe it would be better to hook the ptfs instead? */
569 HistEvent he;
570 int i;
572 _CL_CHECK_ADDHIST(s, goto jleave);
574 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
575 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
576 i = history(_el_hcom, &he, H_NEXT))
577 if (strcmp(he.str, s) == 0) {
578 history(_el_hcom, &he, H_DEL, he.num);
579 break;
581 history(_el_hcom, &he, H_ENTER, s);
582 rele_all_sigs(); /* XXX remove jumps */
583 jleave:
584 # endif
585 UNUSED(s);
588 # ifdef HAVE_HISTORY
589 FL int
590 c_history(void *v)
592 C_HISTORY_SHARED;
594 jlist: {
595 HistEvent he;
596 FILE *fp;
597 char *cp;
598 size_t i, b;
599 int x;
601 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
602 perror("tmpfile");
603 v = NULL;
604 goto jleave;
606 rm(cp);
607 Ftfree(&cp);
609 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
610 b = 0;
611 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
612 x = history(_el_hcom, &he, H_NEXT)) {
613 size_t sl = strlen(he.str);
614 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
615 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
616 --i;
617 b += sl;
620 page_or_print(fp, i);
621 Fclose(fp);
623 goto jleave;
625 jclear: {
626 HistEvent he;
627 history(_el_hcom, &he, H_CLEAR);
629 goto jleave;
631 jentry: {
632 HistEvent he;
633 size_t i;
634 int x;
636 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
637 if (UICMP(z, entry, <=, i)) {
638 entry = (long)i - entry;
639 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
640 x = history(_el_hcom, &he, H_NEXT))
641 if (entry-- == 0) {
642 v = temporary_arg_v_store = UNCONST(he.str);
643 goto jleave;
646 v = NULL;
648 goto jleave;
650 # endif /* HAVE_HISTORY */
651 #endif /* HAVE_EDITLINE */
654 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
656 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
657 * We do not handle character widths because the terminal must deal with that
658 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
659 * characters by definition on the other. We're addicted.
661 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
662 * we're forced to use the very same buffer--the one that is passed through to
663 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
664 * convert that on-the-fly back to the plain char* result once we're done.
665 * To simplify our live, use savestr() buffers for all other needed memory
669 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
670 * TODO NCL: during handler de-/installation handling.
673 #ifdef HAVE_NCL
674 # ifndef MAX_INPUT
675 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
676 # endif
678 /* Since we simply fputs(3) the prompt, assume each character requires two
679 * visual cells -- and we need to restrict the maximum prompt size because
680 * of MAX_INPUT and our desire to have room for some error message left */
681 # define _PROMPT_VLEN(P) (strlen(P) * 2)
682 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
684 union xsighdl {
685 sighandler_type shdl; /* Try avoid races by setting */
686 sl_it sint; /* .sint=-1 when inactive */
688 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
690 struct xtios {
691 struct termios told;
692 struct termios tnew;
695 struct cell {
696 wchar_t wc;
697 ui_it count;
698 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
701 struct line {
702 size_t cursor; /* Current cursor position */
703 size_t topins; /* Outermost cursor col set */
704 union {
705 char * cbuf; /* *x_buf */
706 struct cell * cells;
707 } line;
708 struct str defc; /* Current default content */
709 struct str savec; /* Saved default content */
710 # ifdef HAVE_HISTORY
711 struct hist * hist; /* History cursor */
712 # endif
713 char const * prompt;
714 char const * nd; /* Cursor right */
715 char ** x_buf; /* Caller pointers */
716 size_t * x_bufsize;
719 # ifdef HAVE_HISTORY
720 struct hist {
721 struct hist * older;
722 struct hist * younger;
723 size_t len;
724 char dat[VFIELD_SIZE(sizeof(size_t))];
726 # endif
728 static union xsighdl _ncl_oint;
729 static union xsighdl _ncl_oquit;
730 static union xsighdl _ncl_oterm;
731 static union xsighdl _ncl_ohup;
732 static union xsighdl _ncl_otstp;
733 static union xsighdl _ncl_ottin;
734 static union xsighdl _ncl_ottou;
735 static struct xtios _ncl_tios;
736 # ifdef HAVE_HISTORY
737 static struct hist * _ncl_hist;
738 static struct hist * _ncl_hist_tail;
739 static size_t _ncl_hist_size;
740 static size_t _ncl_hist_size_max;
741 static bool_t _ncl_hist_load;
742 # endif
744 static void _ncl_sigs_up(void);
745 static void _ncl_sigs_down(void);
747 static void _ncl_term_mode(bool_t raw);
749 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
750 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
751 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
752 static ssize_t _ncl_cell2dat(struct line *l);
753 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
754 static void _ncl_cell2save(struct line *l);
755 # endif
757 static void _ncl_khome(struct line *l, bool_t dobell);
758 static void _ncl_kend(struct line *l);
759 static void _ncl_kbs(struct line *l);
760 static void _ncl_kkill(struct line *l, bool_t dobell);
761 static ssize_t _ncl_keof(struct line *l);
762 static void _ncl_kleft(struct line *l);
763 static void _ncl_kright(struct line *l);
764 static void _ncl_krefresh(struct line *l);
765 static void _ncl_kbwddelw(struct line *l);
766 static void _ncl_kgow(struct line *l, ssize_t dir);
767 static void _ncl_kother(struct line *l, wchar_t wc);
768 # ifdef HAVE_HISTORY
769 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
770 static size_t _ncl_khist(struct line *l, bool_t backwd);
771 static size_t _ncl_krhist(struct line *l);
772 # endif
773 # ifdef HAVE_TABEXPAND
774 static size_t _ncl_kht(struct line *l);
775 # endif
776 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
777 size_t len SMALLOC_DEBUG_ARGS);
779 static void
780 _ncl_sigs_up(void)
782 if (_ncl_oint.sint == -1)
783 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
784 if (_ncl_oquit.sint == -1)
785 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
786 if (_ncl_oterm.sint == -1)
787 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
788 if (_ncl_ohup.sint == -1)
789 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
790 if (_ncl_otstp.sint == -1)
791 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
792 if (_ncl_ottin.sint == -1)
793 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
794 if (_ncl_ottou.sint == -1)
795 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
798 static void
799 _ncl_sigs_down(void)
801 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
802 sighandler_type st;
804 if (_ncl_ottou.sint != -1) {
805 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
806 safe_signal(SIGTTOU, st);
808 if (_ncl_ottin.sint != -1) {
809 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
810 safe_signal(SIGTTIN, st);
812 if (_ncl_otstp.sint != -1) {
813 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
814 safe_signal(SIGTSTP, st);
816 if (_ncl_ohup.sint != -1) {
817 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
818 safe_signal(SIGHUP, st);
820 if (_ncl_oterm.sint != -1) {
821 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
822 safe_signal(SIGTERM, st);
824 if (_ncl_oquit.sint != -1) {
825 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
826 safe_signal(SIGQUIT, st);
828 if (_ncl_oint.sint != -1) {
829 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
830 safe_signal(SIGINT, st);
834 static void
835 _ncl_term_mode(bool_t raw)
837 struct termios *tiosp = &_ncl_tios.told;
839 if (!raw)
840 goto jleave;
842 /* Always requery the attributes, in case we've been moved from background
843 * to foreground or however else in between sessions */
844 tcgetattr(STDIN_FILENO, tiosp);
845 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
846 tiosp = &_ncl_tios.tnew;
847 tiosp->c_cc[VMIN] = 1;
848 tiosp->c_cc[VTIME] = 0;
849 tiosp->c_iflag &= ~(ISTRIP);
850 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
851 jleave:
852 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
855 static void
856 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
858 size_t i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
860 if (i > *l->x_bufsize) {
861 i <<= 1;
862 *l->x_bufsize = i;
863 l->line.cbuf =
864 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
868 static void
869 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
871 size_t j;
873 if (i > 0)
874 memmove(cap, cap + 1, i * sizeof(*cap));
876 /* And.. the (rest of the) visual update */
877 for (j = 0; j < i; ++j)
878 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
879 fputs(" \b", stdout);
880 for (j = 0; j < i; ++j)
881 putchar('\b');
884 static ssize_t
885 _ncl_wboundary(struct line *l, ssize_t dir)
887 size_t c = l->cursor, t = l->topins, i;
888 struct cell *cap;
889 bool_t anynon;
891 i = (size_t)-1;
892 if (dir < 0) {
893 if (c == 0)
894 goto jleave;
895 } else if (c == t)
896 goto jleave;
897 else
898 --t, --c; /* Unsigned wrapping may occur (twice), then */
900 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
901 wchar_t wc = cap[c + dir].wc;
902 if (iswblank(wc) || iswpunct(wc)) {
903 if (anynon)
904 break;
905 } else
906 anynon = TRU1;
907 ++i;
908 c += dir;
909 if (dir < 0) {
910 if (c == 0)
911 break;
912 } else if (c == t)
913 break;
915 jleave:
916 return (ssize_t)i;
919 static ssize_t
920 _ncl_cell2dat(struct line *l)
922 size_t len = 0, i;
924 if (l->topins > 0)
925 for (i = 0; i < l->topins; ++i) {
926 struct cell *cap = l->line.cells + i;
927 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
928 len += cap->count;
930 l->line.cbuf[len] = '\0';
931 return (ssize_t)len;
934 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
935 static void
936 _ncl_cell2save(struct line *l)
938 size_t len, i;
939 struct cell *cap;
941 l->savec.s = NULL, l->savec.l = 0;
942 if (l->topins == 0)
943 goto jleave;
945 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
946 len += cap->count;
948 l->savec.l = len;
949 l->savec.s = salloc(len + 1);
951 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
952 memcpy(l->savec.s + len, cap->cbuf, cap->count);
953 len += cap->count;
955 l->savec.s[len] = '\0';
956 jleave:
959 # endif
961 static void
962 _ncl_khome(struct line *l, bool_t dobell)
964 size_t c = l->cursor;
966 if (c > 0) {
967 l->cursor = 0;
968 while (c-- != 0)
969 putchar('\b');
970 } else if (dobell)
971 putchar('\a');
974 static void
975 _ncl_kend(struct line *l)
977 ssize_t i = (ssize_t)(l->topins - l->cursor);
979 if (i > 0) {
980 l->cursor = l->topins;
981 while (i-- != 0)
982 fputs(l->nd, stdout);
983 } else
984 putchar('\a');
987 static void
988 _ncl_kbs(struct line *l)
990 ssize_t c = l->cursor, t = l->topins;
992 if (c > 0) {
993 putchar('\b');
994 l->cursor = --c;
995 l->topins = --t;
996 t -= c;
997 _ncl_bs_eof_dvup(l->line.cells + c, t);
998 } else
999 putchar('\a');
1002 static void
1003 _ncl_kkill(struct line *l, bool_t dobell)
1005 size_t j, c = l->cursor, i = (size_t)(l->topins - c);
1007 if (i > 0) {
1008 l->topins = c;
1009 for (j = i; j != 0; --j)
1010 putchar(' ');
1011 for (j = i; j != 0; --j)
1012 putchar('\b');
1013 } else if (dobell)
1014 putchar('\a');
1017 static ssize_t
1018 _ncl_keof(struct line *l)
1020 size_t c = l->cursor, t = l->topins;
1021 ssize_t i = (ssize_t)(t - c);
1023 if (i > 0) {
1024 l->topins = --t;
1025 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1026 } else if (t == 0 && !ok_blook(ignoreeof)) {
1027 fputs("^D", stdout);
1028 fflush(stdout);
1029 i = -1;
1030 } else {
1031 putchar('\a');
1032 i = 0;
1034 return i;
1037 static void
1038 _ncl_kleft(struct line *l)
1040 if (l->cursor > 0) {
1041 --l->cursor;
1042 putchar('\b');
1043 } else
1044 putchar('\a');
1047 static void
1048 _ncl_kright(struct line *l)
1050 if (l->cursor < l->topins) {
1051 ++l->cursor;
1052 fputs(l->nd, stdout);
1053 } else
1054 putchar('\a');
1057 static void
1058 _ncl_krefresh(struct line *l)
1060 struct cell *cap;
1061 size_t i;
1063 putchar('\r');
1064 if (l->prompt != NULL && *l->prompt != '\0')
1065 fputs(l->prompt, stdout);
1066 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1067 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1068 for (i = l->topins - l->cursor; i > 0; --i)
1069 putchar('\b');
1072 static void
1073 _ncl_kbwddelw(struct line *l)
1075 ssize_t i;
1076 size_t c = l->cursor, t, j;
1077 struct cell *cap;
1079 i = _ncl_wboundary(l, -1);
1080 if (i <= 0) {
1081 if (i < 0)
1082 putchar('\a');
1083 goto jleave;
1086 c = l->cursor - i;
1087 t = l->topins;
1088 l->topins = t - i;
1089 l->cursor = c;
1090 cap = l->line.cells + c;
1092 if (t != l->cursor) {
1093 j = t - c + i;
1094 memmove(cap, cap + i, j * sizeof(*cap));
1097 for (j = i; j > 0; --j)
1098 putchar('\b');
1099 for (j = l->topins - c; j > 0; ++cap, --j)
1100 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1101 for (j = i; j > 0; --j)
1102 putchar(' ');
1103 for (j = t - c; j > 0; --j)
1104 putchar('\b');
1105 jleave:
1109 static void
1110 _ncl_kgow(struct line *l, ssize_t dir)
1112 ssize_t i = _ncl_wboundary(l, dir);
1113 if (i <= 0) {
1114 if (i < 0)
1115 putchar('\a');
1116 goto jleave;
1119 if (dir < 0) {
1120 l->cursor -= i;
1121 while (i-- > 0)
1122 putchar('\b');
1123 } else {
1124 l->cursor += i;
1125 while (i-- > 0)
1126 fputs(l->nd, stdout);
1128 jleave:
1132 static void
1133 _ncl_kother(struct line *l, wchar_t wc)
1135 /* Append if at EOL, insert otherwise;
1136 * since we may move around character-wise, always use a fresh ps */
1137 mbstate_t ps;
1138 struct cell cell, *cap;
1139 size_t i, c;
1141 /* First init a cell and see wether we'll really handle this wc */
1142 cell.wc = wc;
1143 memset(&ps, 0, sizeof ps);
1144 i = wcrtomb(cell.cbuf, wc, &ps);
1145 if (i > MB_LEN_MAX)
1146 goto jleave;
1147 cell.count = (ui_it)i;
1148 if (enc_has_state) {
1149 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1150 if (i == 1)
1152 else if (--i < MB_LEN_MAX)
1153 cell.count += (ui_it)i;
1154 else
1155 goto jleave;
1158 /* Yes, we will! Place it in the array */
1159 c = l->cursor++;
1160 i = l->topins++ - c;
1161 cap = l->line.cells + c;
1162 if (i > 0)
1163 memmove(cap + 1, cap, i * sizeof(cell));
1164 memcpy(cap, &cell, sizeof cell);
1166 /* And update visual */
1167 c = i;
1169 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1170 while ((++cap, i-- != 0));
1171 while (c-- != 0)
1172 putchar('\b');
1173 jleave:
1177 # ifdef HAVE_HISTORY
1178 static size_t
1179 __ncl_khist_shared(struct line *l, struct hist *hp)
1181 size_t rv;
1183 if ((l->hist = hp) != NULL) {
1184 l->defc.s = savestrbuf(hp->dat, hp->len);
1185 rv =
1186 l->defc.l = hp->len;
1187 if (l->topins > 0) {
1188 _ncl_khome(l, FAL0);
1189 _ncl_kkill(l, FAL0);
1191 } else {
1192 putchar('\a');
1193 rv = 0;
1195 return rv;
1198 static size_t
1199 _ncl_khist(struct line *l, bool_t backwd)
1201 struct hist *hp;
1203 /* If we're not in history mode yet, save line content;
1204 * also, disallow forward search, then, and, of course, bail unless we
1205 * do have any history at all */
1206 if ((hp = l->hist) == NULL) {
1207 if (!backwd)
1208 goto jleave;
1209 if ((hp = _ncl_hist) == NULL)
1210 goto jleave;
1211 _ncl_cell2save(l);
1212 goto jleave;
1215 hp = backwd ? hp->older : hp->younger;
1216 jleave:
1217 return __ncl_khist_shared(l, hp);
1220 static size_t
1221 _ncl_krhist(struct line *l)
1223 struct str orig_savec;
1224 struct hist *hp = NULL;
1226 /* We cannot complete an empty line */
1227 if (l->topins == 0) {
1228 /* XXX The upcoming hard reset would restore a set savec buffer,
1229 * XXX so forcefully reset that. A cleaner solution would be to
1230 * XXX reset it whenever a restore is no longer desired */
1231 l->savec.s = NULL, l->savec.l = 0;
1232 goto jleave;
1234 if ((hp = l->hist) == NULL) {
1235 if ((hp = _ncl_hist) == NULL)
1236 goto jleave;
1237 orig_savec.s = NULL;
1238 orig_savec.l = 0; /* silence CC */
1239 } else if ((hp = hp->older) == NULL)
1240 goto jleave;
1241 else
1242 orig_savec = l->savec;
1244 if (orig_savec.s == NULL)
1245 _ncl_cell2save(l);
1246 for (; hp != NULL; hp = hp->older)
1247 if (is_prefix(l->savec.s, hp->dat))
1248 break;
1249 if (orig_savec.s != NULL)
1250 l->savec = orig_savec;
1251 jleave:
1252 return __ncl_khist_shared(l, hp);
1254 # endif
1256 # ifdef HAVE_TABEXPAND
1257 static size_t
1258 _ncl_kht(struct line *l)
1260 struct str orig, bot, topp, sub, exp;
1261 struct cell *cword, *ctop, *cx;
1262 bool_t set_savec = FAL0;
1263 size_t rv = 0;
1265 /* We cannot expand an empty line */
1266 if (l->topins == 0)
1267 goto jleave;
1269 /* Get plain line data; if this is the first expansion/xy, update the
1270 * very original content so that ^G gets the origin back */
1271 orig = l->savec;
1272 _ncl_cell2save(l);
1273 exp = l->savec;
1274 if (orig.s != NULL)
1275 l->savec = orig;
1276 else
1277 set_savec = TRU1;
1278 orig = exp;
1280 cword = l->line.cells;
1281 ctop = cword + l->cursor;
1283 /* topp: separate data right of cursor */
1284 if ((cx = cword + l->topins) != ctop) {
1285 for (rv = 0; cx > ctop; --cx)
1286 rv += cx->count;
1287 topp.l = rv;
1288 topp.s = orig.s + orig.l - rv;
1289 } else
1290 topp.s = NULL, topp.l = 0;
1292 /* bot, sub: we cannot expand the entire data left of cursor, but only
1293 * the last "word", so separate them */
1294 while (cx > cword && !iswspace(cx[-1].wc))
1295 --cx;
1296 for (rv = 0; cword < cx; ++cword)
1297 rv += cword->count;
1298 sub =
1299 bot = orig;
1300 bot.l = rv;
1301 sub.s += rv;
1302 sub.l -= rv;
1303 sub.l -= topp.l;
1305 /* Leave room for "implicit asterisk" expansion, as below */
1306 if (sub.l == 0) {
1307 sub.s = UNCONST("*");
1308 sub.l = 1;
1309 } else {
1310 exp.s = salloc(sub.l + 1 +1);
1311 memcpy(exp.s, sub.s, sub.l);
1312 exp.s[sub.l] = '\0';
1313 sub.s = exp.s;
1316 /* TODO there is a TODO note upon fexpand() with multi-return;
1317 * TODO if that will change, the if() below can be simplified */
1318 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1319 jredo:
1320 hold_all_sigs();
1321 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1322 rele_all_sigs();
1324 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1325 goto jnope;
1326 /* If the expansion equals the original string, assume the user wants what
1327 * is usually known as tab completion, append `*' and restart */
1328 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1329 if (sub.s[sub.l - 1] == '*')
1330 goto jnope;
1331 sub.s[sub.l++] = '*';
1332 sub.s[sub.l] = '\0';
1333 goto jredo;
1336 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1337 * Take care to take *prompt* into account, since we don't know
1338 * anything about it's visual length (fputs(3) is used), simply
1339 * assume each character requires two columns */
1340 /* TODO the problem is that we loose control otherwise; in the best
1341 * TODO case the user can control via ^A and ^K etc., but be safe;
1342 * TODO we cannot simply adjust fexpand() because we don't know how
1343 * TODO that is implemented... The real solution would be to check
1344 * TODO wether we fit on a line, and start a pager if not.
1345 * TODO However, that should be part of a real tab-COMPLETION, then,
1346 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1347 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1348 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1349 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1350 char const e1[] = "[maximum line size exceeded]";
1351 exp.s = UNCONST(e1);
1352 exp.l = sizeof(e1) - 1;
1353 topp.l = 0;
1354 if (rv + bot.l + exp.l >= MAX_INPUT)
1355 bot.l = 0;
1356 if (rv + exp.l >= MAX_INPUT) {
1357 char const e2[] = "[ERR]";
1358 exp.s = UNCONST(e2);
1359 exp.l = sizeof(e2) - 1;
1363 orig.l = bot.l + exp.l + topp.l;
1364 orig.s = salloc(orig.l + 1 + 5);
1365 if ((rv = bot.l) > 0)
1366 memcpy(orig.s, bot.s, rv);
1367 memcpy(orig.s + rv, exp.s, exp.l);
1368 rv += exp.l;
1369 if (topp.l > 0) {
1370 memcpy(orig.s + rv, topp.s, topp.l);
1371 rv += topp.l;
1373 orig.s[rv] = '\0';
1375 l->defc = orig;
1376 _ncl_khome(l, FAL0);
1377 _ncl_kkill(l, FAL0);
1378 jleave:
1379 return rv;
1380 jnope:
1381 /* If we've provided a default content, but failed to expand, there is
1382 * nothing we can "revert to": drop that default again */
1383 if (set_savec)
1384 l->savec.s = NULL, l->savec.l = 0;
1385 rv = 0;
1386 goto jleave;
1388 # endif /* HAVE_TABEXPAND */
1390 static ssize_t
1391 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1392 SMALLOC_DEBUG_ARGS)
1394 /* We want to save code, yet we may have to incorporate a lines'
1395 * default content and / or default input to switch back to after some
1396 * history movement; let "len > 0" mean "have to display some data
1397 * buffer", and only otherwise read(2) it */
1398 mbstate_t ps[2];
1399 struct line l;
1400 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
1401 wchar_t wc;
1402 ssize_t rv;
1403 ui32_t maybe_cursor;
1405 memset(&l, 0, sizeof l);
1406 l.line.cbuf = *buf;
1407 if (len != 0) {
1408 l.defc.s = savestrbuf(*buf, len);
1409 l.defc.l = len;
1411 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1412 l.prompt = prompt = "?ERR?";
1413 /* TODO *l.nd=='\0' only because we have no value-cache -> see acmava.c */
1414 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1415 l.nd = "\033[C"; /* XXX no "magic" constant */
1416 l.x_buf = buf;
1417 l.x_bufsize = bufsize;
1419 if (prompt != NULL && *prompt != '\0') {
1420 fputs(prompt, stdout);
1421 fflush(stdout);
1423 jrestart:
1424 memset(ps, 0, sizeof ps);
1425 maybe_cursor = 0;
1426 /* TODO: NCL: we should output the reset sequence when we jrestart:
1427 * TODO: NCL: if we are using a stateful encoding? !
1428 * TODO: NCL: in short: this is not yet well understood */
1429 for (;;) {
1430 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1432 /* Normal read(2)? Else buffer-takeover: speed this one up */
1433 if (len == 0)
1434 cbufp =
1435 cbuf = cbuf_base;
1436 else {
1437 assert(l.defc.l > 0 && l.defc.s != NULL);
1438 cbufp =
1439 cbuf = l.defc.s + (l.defc.l - len);
1440 cbufp += len;
1443 /* Read in the next complete multibyte character */
1444 for (;;) {
1445 if (len == 0) {
1446 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1447 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1448 continue;
1449 goto jleave;
1451 ++cbufp;
1454 /* Ach! the ISO C multibyte handling!
1455 * Encodings with locking shift states cannot really be helped, since
1456 * it is impossible to only query the shift state, as opposed to the
1457 * entire shift state + character pair (via ISO C functions) */
1458 rv = (ssize_t)mbrtowc(&wc, cbuf, (size_t)(cbufp - cbuf), ps + 0);
1459 if (rv <= 0) {
1460 /* Any error during take-over can only result in a hard reset;
1461 * Otherwise, if it's a hard error, or if too many redundant shift
1462 * sequences overflow our buffer, also perform a hard reset */
1463 if (len != 0 || rv == -1 ||
1464 sizeof cbuf_base == (size_t)(cbufp - cbuf)) {
1465 l.savec.s = l.defc.s = NULL,
1466 l.savec.l = l.defc.l = len = 0;
1467 putchar('\a');
1468 wc = 'G';
1469 goto jreset;
1471 /* Otherwise, due to the way we deal with the buffer, we need to
1472 * restore the mbstate_t from before this conversion */
1473 ps[0] = ps[1];
1474 continue;
1477 if (len != 0 && (len -= (size_t)rv) == 0)
1478 l.defc.s = NULL, l.defc.l = 0;
1479 ps[1] = ps[0];
1480 break;
1483 /* Don't interpret control bytes during buffer take-over */
1484 if (cbuf != cbuf_base)
1485 goto jprint;
1486 switch (wc) {
1487 case 'A' ^ 0x40: /* cursor home */
1488 _ncl_khome(&l, TRU1);
1489 break;
1490 j_b:
1491 case 'B' ^ 0x40: /* backward character */
1492 _ncl_kleft(&l);
1493 break;
1494 /* 'C': interrupt (CTRL-C) */
1495 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1496 if ((rv = _ncl_keof(&l)) < 0)
1497 goto jleave;
1498 break;
1499 case 'E' ^ 0x40: /* end of line */
1500 _ncl_kend(&l);
1501 break;
1502 j_f:
1503 case 'F' ^ 0x40: /* forward character */
1504 _ncl_kright(&l);
1505 break;
1506 /* 'G' below */
1507 case 'H' ^ 0x40: /* backspace */
1508 case '\177':
1509 _ncl_kbs(&l);
1510 break;
1511 case 'I' ^ 0x40: /* horizontal tab */
1512 # ifdef HAVE_TABEXPAND
1513 if ((len = _ncl_kht(&l)) > 0)
1514 goto jrestart;
1515 # endif
1516 goto jbell;
1517 case 'J' ^ 0x40: /* NL (\n) */
1518 goto jdone;
1519 case 'G' ^ 0x40: /* full reset */
1520 jreset:
1521 /* FALLTHRU */
1522 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1523 _ncl_khome(&l, FAL0);
1524 /* FALLTHRU */
1525 case 'K' ^ 0x40: /* kill from cursor to end of line */
1526 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1527 /* (Handle full reset?) */
1528 if (wc == ('G' ^ 0x40)) {
1529 # ifdef HAVE_HISTORY
1530 l.hist = NULL;
1531 # endif
1532 if ((len = l.savec.l) != 0) {
1533 l.defc = l.savec;
1534 l.savec.s = NULL, l.savec.l = 0;
1535 } else
1536 len = l.defc.l;
1538 fflush(stdout);
1539 goto jrestart;
1540 case 'L' ^ 0x40: /* repaint line */
1541 _ncl_krefresh(&l);
1542 break;
1543 /* 'M': CR (\r) */
1544 j_n:
1545 case 'N' ^ 0x40: /* history next */
1546 # ifdef HAVE_HISTORY
1547 if (l.hist == NULL)
1548 goto jbell;
1549 if ((len = _ncl_khist(&l, FAL0)) > 0)
1550 goto jrestart;
1551 wc = 'G' ^ 0x40;
1552 goto jreset;
1553 # else
1554 goto jbell;
1555 # endif
1556 /* 'O' */
1557 j_p:
1558 case 'P' ^ 0x40: /* history previous */
1559 # ifdef HAVE_HISTORY
1560 if ((len = _ncl_khist(&l, TRU1)) > 0)
1561 goto jrestart;
1562 wc = 'G' ^ 0x40;
1563 goto jreset;
1564 # else
1565 goto jbell;
1566 # endif
1567 /* 'Q': no code */
1568 case 'R' ^ 0x40: /* reverse history search */
1569 # ifdef HAVE_HISTORY
1570 if ((len = _ncl_krhist(&l)) > 0)
1571 goto jrestart;
1572 wc = 'G' ^ 0x40;
1573 goto jreset;
1574 # else
1575 goto jbell;
1576 # endif
1577 /* 'S': no code */
1578 /* 'U' above */
1579 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1580 case 'W' ^ 0x40: /* backward delete "word" */
1581 _ncl_kbwddelw(&l);
1582 break;
1583 case 'X' ^ 0x40: /* move cursor forward "word" */
1584 _ncl_kgow(&l, +1);
1585 break;
1586 case 'Y' ^ 0x40: /* move cursor backward "word" */
1587 _ncl_kgow(&l, -1);
1588 break;
1589 /* 'Z': suspend (CTRL-Z) */
1590 case 0x1B:
1591 if (maybe_cursor++ != 0)
1592 goto jreset;
1593 continue;
1594 default:
1595 /* XXX Handle usual ^[[[ABCD] cursor keys -- UGLY, "MAGIC", INFLEX */
1596 if (maybe_cursor > 0) {
1597 if (++maybe_cursor == 2) {
1598 if (wc == L'[')
1599 continue;
1600 maybe_cursor = 0;
1601 } else {
1602 maybe_cursor = 0;
1603 switch (wc) {
1604 case L'A': goto j_p;
1605 case L'B': goto j_n;
1606 case L'C': goto j_f;
1607 case L'D': goto j_b;
1609 _ncl_kother(&l, L'[');
1612 jprint:
1613 if (iswprint(wc)) {
1614 _ncl_kother(&l, wc);
1615 /* Don't clear the history during takeover..
1616 * ..and also avoid fflush()ing unless we've
1617 * worked the entire buffer */
1618 if (len > 0)
1619 continue;
1620 # ifdef HAVE_HISTORY
1621 if (cbuf == cbuf_base)
1622 l.hist = NULL;
1623 # endif
1624 } else {
1625 jbell:
1626 putchar('\a');
1628 break;
1630 fflush(stdout);
1633 /* We have a completed input line, convert the struct cell data to its
1634 * plain character equivalent */
1635 jdone:
1636 putchar('\n');
1637 fflush(stdout);
1638 len = _ncl_cell2dat(&l);
1639 rv = (ssize_t)len;
1640 jleave:
1641 return rv;
1644 FL void
1645 tty_init(void)
1647 # ifdef HAVE_HISTORY
1648 long hs;
1649 char *v, *lbuf;
1650 FILE *f;
1651 size_t lsize, cnt, llen;
1652 # endif
1654 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1655 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1656 _ncl_ottou.sint = -1;
1658 # ifdef HAVE_HISTORY
1659 _CL_HISTSIZE(hs);
1660 _ncl_hist_size = 0;
1661 _ncl_hist_size_max = hs;
1662 if (hs == 0)
1663 goto jleave;
1665 _CL_HISTFILE(v);
1666 if (v == NULL)
1667 goto jleave;
1669 hold_all_sigs(); /* XXX too heavy, yet we may jump even here!? */
1670 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1671 if (f == NULL)
1672 goto jdone;
1674 lbuf = NULL;
1675 lsize = 0;
1676 cnt = fsize(f);
1677 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1678 if (llen > 0 && lbuf[llen - 1] == '\n')
1679 lbuf[--llen] = '\0';
1680 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1681 continue;
1682 _ncl_hist_load = TRU1;
1683 tty_addhist(lbuf);
1684 _ncl_hist_load = FAL0;
1686 if (lbuf != NULL)
1687 free(lbuf);
1689 fclose(f);
1690 jdone:
1691 rele_all_sigs(); /* XXX remove jumps */
1692 jleave:
1693 # endif /* HAVE_HISTORY */
1697 FL void
1698 tty_destroy(void)
1700 # ifdef HAVE_HISTORY
1701 long hs;
1702 char *v;
1703 struct hist *hp;
1704 FILE *f;
1706 _CL_HISTSIZE(hs);
1707 if (hs == 0)
1708 goto jleave;
1710 _CL_HISTFILE(v);
1711 if (v == NULL)
1712 goto jleave;
1714 if ((hp = _ncl_hist) != NULL)
1715 while (hp->older != NULL && hs-- != 0)
1716 hp = hp->older;
1718 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1719 f = fopen(v, "w"); /* TODO temporary + rename?! */
1720 if (f == NULL)
1721 goto jdone;
1722 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1723 goto jclose;
1725 for (; hp != NULL; hp = hp->younger) {
1726 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1727 putc('\n', f);
1729 jclose:
1730 fclose(f);
1731 jdone:
1732 rele_all_sigs(); /* XXX remove jumps */
1733 jleave:
1734 # endif /* HAVE_HISTORY */
1738 FL void
1739 tty_signal(int sig)
1741 sigset_t nset, oset;
1743 switch (sig) {
1744 case SIGWINCH:
1745 /* We don't deal with SIGWINCH, yet get called from main.c */
1746 break;
1747 default:
1748 _ncl_term_mode(FAL0);
1749 _ncl_sigs_down();
1750 sigemptyset(&nset);
1751 sigaddset(&nset, sig);
1752 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1753 kill(0, sig);
1754 /* When we come here we'll continue editing, so reestablish */
1755 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1756 _ncl_sigs_up();
1757 _ncl_term_mode(TRU1);
1758 break;
1762 FL int
1763 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1764 SMALLOC_DEBUG_ARGS)
1766 ssize_t nn;
1768 /* Of course we have races here, but they cannot be avoided on POSIX
1769 * (except by even *more* actions) */
1770 _ncl_sigs_up();
1771 _ncl_term_mode(TRU1);
1772 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1773 _ncl_term_mode(FAL0);
1774 _ncl_sigs_down();
1776 return (int)nn;
1779 FL void
1780 tty_addhist(char const *s)
1782 # ifdef HAVE_HISTORY
1783 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1784 size_t l = strlen(s);
1785 struct hist *h, *o, *y;
1787 if (_ncl_hist_size_max == 0)
1788 goto j_leave;
1789 _CL_CHECK_ADDHIST(s, goto j_leave);
1791 /* Eliminating duplicates is expensive, but simply inacceptable so
1792 * during the load of a potentially large history file! */
1793 if (!_ncl_hist_load)
1794 for (h = _ncl_hist; h != NULL; h = h->older)
1795 if (h->len == l && !strcmp(h->dat, s)) {
1796 hold_all_sigs(); /* TODO */
1797 o = h->older;
1798 y = h->younger;
1799 if (o != NULL)
1800 o->younger = y;
1801 else
1802 _ncl_hist_tail = y;
1803 if (y != NULL)
1804 y->older = o;
1805 else
1806 _ncl_hist = o;
1807 goto jleave;
1809 hold_all_sigs();
1811 ++_ncl_hist_size;
1812 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1813 --_ncl_hist_size;
1814 if ((h = _ncl_hist_tail) != NULL) {
1815 if ((_ncl_hist_tail = h->younger) == NULL)
1816 _ncl_hist = NULL;
1817 else
1818 _ncl_hist_tail->older = NULL;
1819 free(h);
1823 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1824 h->len = l;
1825 memcpy(h->dat, s, l +1);
1826 jleave:
1827 if ((h->older = _ncl_hist) != NULL)
1828 _ncl_hist->younger = h;
1829 else
1830 _ncl_hist_tail = h;
1831 h->younger = NULL;
1832 _ncl_hist = h;
1834 rele_all_sigs();
1835 j_leave:
1836 # endif
1837 UNUSED(s);
1840 # ifdef HAVE_HISTORY
1841 FL int
1842 c_history(void *v)
1844 C_HISTORY_SHARED;
1846 jlist: {
1847 FILE *fp;
1848 char *cp;
1849 size_t i, b;
1850 struct hist *h;
1852 if (_ncl_hist == NULL)
1853 goto jleave;
1855 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1856 perror("tmpfile");
1857 v = NULL;
1858 goto jleave;
1860 rm(cp);
1861 Ftfree(&cp);
1863 i = _ncl_hist_size;
1864 b = 0;
1865 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
1866 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
1867 (ul_it)i, h->dat, (ul_it)b, (ul_it)h->len);
1869 page_or_print(fp, i);
1870 Fclose(fp);
1872 goto jleave;
1874 jclear: {
1875 struct hist *h;
1876 while ((h = _ncl_hist) != NULL) {
1877 _ncl_hist = h->older;
1878 free(h);
1880 _ncl_hist_tail = NULL;
1881 _ncl_hist_size = 0;
1883 goto jleave;
1885 jentry: {
1886 struct hist *h = _ncl_hist;
1887 if (UICMP(z, entry, <=, _ncl_hist_size)) {
1888 entry = (long)_ncl_hist_size - entry;
1889 for (h = _ncl_hist;; h = h->older)
1890 if (h == NULL)
1891 break;
1892 else if (entry-- != 0)
1893 continue;
1894 else {
1895 v = temporary_arg_v_store = h->dat;
1896 goto jleave;
1899 v = NULL;
1901 goto jleave;
1903 # endif /* HAVE_HISTORY */
1904 #endif /* HAVE_NCL */
1907 * The really-nothing-at-all implementation
1910 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
1911 FL void
1912 tty_init(void)
1915 FL void
1916 tty_destroy(void)
1919 FL void
1920 tty_signal(int sig)
1922 UNUSED(sig);
1925 FL int
1926 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1927 SMALLOC_DEBUG_ARGS)
1930 * TODO The nothing-at-all tty layer even forces re-entering all the
1931 * TODO original data when re-editing a field
1933 bool_t doffl = FAL0;
1935 if (prompt != NULL && *prompt != '\0') {
1936 fputs(prompt, stdout);
1937 doffl = TRU1;
1939 if (n > 0) {
1940 fprintf(stdout, tr(511, "{former content: %.*s} "), (int)n, *linebuf);
1941 n = 0;
1942 doffl = TRU1;
1944 if (doffl)
1945 fflush(stdout);
1946 return (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
1949 FL void
1950 tty_addhist(char const *s)
1952 UNUSED(s);
1954 #endif /* nothing at all */
1956 /* vim:set fenc=utf-8:s-it-mode */