cmd1.c:_type1(): do not unset $LESS unless it was our own..
[s-mailx.git] / tty.c
blob832723c93da73872bdd4d57f03febe691754871d
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;\
104 if (*argv == NULL)\
105 goto jlist;\
106 if (argv[1] != NULL)\
107 goto jerr;\
108 if (asccasecmp(*argv, "show") == 0)\
109 goto jlist;\
110 if (asccasecmp(*argv, "clear") == 0)\
111 goto jclear;\
112 jerr:\
113 fprintf(stderr, "Synopsis: history: %s\n", tr(431,\
114 "Either <show> (default) or <clear> the line editor history"));\
115 v = NULL;\
116 jleave:\
117 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
118 #endif /* HAVE_HISTORY */
120 /* fexpand() flags for expand-on-tab */
121 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
124 * Because we have multiple identical implementations, change file layout a bit
125 * and place the implementations one after the other below the other externals
128 FL bool_t
129 yorn(char const *msg)
131 char *cp;
133 if (!(options & OPT_INTERACTIVE))
134 return TRU1;
135 do if ((cp = readstr_input(msg, NULL)) == NULL)
136 return FAL0;
137 while (*cp != 'y' && *cp != 'Y' && *cp != 'n' && *cp != 'N');
138 return (*cp == 'y' || *cp == 'Y');
141 FL char *
142 getuser(char const *query)
144 char *user = NULL;
146 if (query == NULL)
147 query = tr(509, "User: ");
149 if (readline_input(LNED_NONE, query, &termios_state.ts_linebuf,
150 &termios_state.ts_linesize) >= 0)
151 user = termios_state.ts_linebuf;
152 termios_state_reset();
153 return user;
156 FL char *
157 getpassword(char const *query) /* FIXME encaps ttystate signal safe */
159 struct termios tios;
160 char *pass = NULL;
162 if (query == NULL)
163 query = tr(510, "Password: ");
164 fputs(query, stdout);
165 fflush(stdout);
168 * FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
169 * foreground pgrp, and can fail with EINTR!!
171 if (options & OPT_TTYIN) {
172 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
173 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
174 termios_state.ts_needs_reset = TRU1;
175 tios.c_iflag &= ~(ISTRIP);
176 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
177 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
180 if (readline_restart(stdin, &termios_state.ts_linebuf,
181 &termios_state.ts_linesize, 0) >= 0)
182 pass = termios_state.ts_linebuf;
183 termios_state_reset();
185 if (options & OPT_TTYIN)
186 fputc('\n', stdout);
187 return pass;
190 FL bool_t
191 getcredentials(char **user, char **pass)
193 bool_t rv = TRU1;
194 char *u = *user, *p = *pass;
196 if (u == NULL) {
197 if ((u = getuser(NULL)) == NULL)
198 rv = FAL0;
199 else if (p == NULL)
200 u = savestr(u);
201 *user = u;
204 if (p == NULL) {
205 if ((p = getpassword(NULL)) == NULL)
206 rv = FAL0;
207 *pass = p;
209 return rv;
213 * readline(3)
216 #ifdef HAVE_READLINE
217 static sighandler_type _rl_shup;
218 static char * _rl_buf; /* pre_input() hook: initial line */
219 static int _rl_buflen; /* content, and its length */
221 static int _rl_pre_input(void);
223 static int
224 _rl_pre_input(void)
226 /* Handle leftover data from \ escaped former line */
227 rl_extend_line_buffer(_rl_buflen + 10);
228 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
229 rl_point = rl_end = _rl_buflen;
230 rl_pre_input_hook = (rl_hook_func_t*)NULL;
231 rl_redisplay();
232 return 0;
235 FL void
236 tty_init(void)
238 # ifdef HAVE_HISTORY
239 long hs;
240 char *v;
241 # endif
243 rl_readline_name = UNCONST(uagent);
244 # ifdef HAVE_HISTORY
245 _CL_HISTSIZE(hs);
246 using_history();
247 stifle_history((int)hs);
248 # endif
249 rl_read_init_file(NULL);
251 /* Because rl_read_init_file() may have introduced yet a different
252 * history size limit, simply load and incorporate the history, leave
253 * it up to readline(3) to do the rest */
254 # ifdef HAVE_HISTORY
255 _CL_HISTFILE(v);
256 if (v != NULL)
257 read_history(v);
258 # endif
261 FL void
262 tty_destroy(void)
264 # ifdef HAVE_HISTORY
265 char *v;
267 _CL_HISTFILE(v);
268 if (v != NULL)
269 write_history(v);
270 # endif
274 FL void
275 tty_signal(int sig)
277 sigset_t nset, oset;
279 switch (sig) {
280 # ifdef SIGWINCH
281 case SIGWINCH:
282 break;
283 # endif
284 case SIGHUP:
285 /* readline(3) doesn't catch it :( */
286 rl_free_line_state();
287 rl_cleanup_after_signal();
288 safe_signal(SIGHUP, _rl_shup);
289 sigemptyset(&nset);
290 sigaddset(&nset, sig);
291 sigprocmask(SIG_UNBLOCK, &nset, &oset);
292 kill(0, sig);
293 /* XXX When we come here we'll continue editing, so reestablish
294 * XXX cannot happen */
295 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
296 _rl_shup = safe_signal(SIGHUP, &tty_signal);
297 rl_reset_after_signal();
298 break;
299 default:
300 break;
304 FL int
305 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
306 SMALLOC_DEBUG_ARGS)
308 int nn;
309 char *line;
311 if (n > 0) {
312 _rl_buf = *linebuf;
313 _rl_buflen = (int)n;
314 rl_pre_input_hook = &_rl_pre_input;
317 _rl_shup = safe_signal(SIGHUP, &tty_signal);
318 line = readline(prompt != NULL ? prompt : "");
319 safe_signal(SIGHUP, _rl_shup);
321 if (line == NULL) {
322 nn = -1;
323 goto jleave;
325 n = strlen(line);
327 if (n >= *linesize) {
328 *linesize = LINESIZE + n + 1;
329 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
331 memcpy(*linebuf, line, n);
332 (free)(line);
333 (*linebuf)[n] = '\0';
334 nn = (int)n;
335 jleave:
336 return nn;
339 FL void
340 tty_addhist(char const *s)
342 # ifdef HAVE_HISTORY
343 _CL_CHECK_ADDHIST(s, goto jleave);
344 hold_all_sigs(); /* XXX too heavy */
345 add_history(s); /* XXX yet we jump away! */
346 rele_all_sigs(); /* XXX remove jumps */
347 jleave:
348 # endif
349 UNUSED(s);
352 # ifdef HAVE_HISTORY
353 FL int
354 c_history(void *v)
356 C_HISTORY_SHARED;
358 jlist: {
359 FILE *fp;
360 char *cp;
361 HIST_ENTRY **hl;
362 size_t i, b;
364 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
365 perror("tmpfile");
366 v = NULL;
367 goto jleave;
369 rm(cp);
370 Ftfree(&cp);
372 hl = history_list();
373 i = 0;
374 if (hl != NULL)
375 while (hl[i] != NULL)
376 ++i;
378 b = 0;
379 while (i-- > 0) {
380 size_t sl = strlen(hl[i]->line);
381 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
382 (ul_it)i, hl[i]->line, (ul_it)b, (ul_it)sl);
383 b += sl;
386 page_or_print(fp, i);
387 Fclose(fp);
389 goto jleave;
391 jclear:
392 clear_history();
393 goto jleave;
395 # endif /* HAVE_HISTORY */
396 #endif /* HAVE_READLINE */
399 * BSD editline(3)
402 #ifdef HAVE_EDITLINE
403 static EditLine * _el_el; /* editline(3) handle */
404 static char const * _el_prompt; /* Current prompt */
405 # ifdef HAVE_HISTORY
406 static History * _el_hcom; /* History handle for commline */
407 # endif
409 static char const * _el_getprompt(void);
411 static char const *
412 _el_getprompt(void)
414 return _el_prompt;
417 FL void
418 tty_init(void)
420 # ifdef HAVE_HISTORY
421 HistEvent he;
422 long hs;
423 char *v;
424 # endif
426 # ifdef HAVE_HISTORY
427 _CL_HISTSIZE(hs);
428 _el_hcom = history_init();
429 history(_el_hcom, &he, H_SETSIZE, (int)hs);
430 history(_el_hcom, &he, H_SETUNIQUE, 1);
431 # endif
433 _el_el = el_init(uagent, stdin, stdout, stderr);
434 el_set(_el_el, EL_SIGNAL, 1);
435 el_set(_el_el, EL_TERMINAL, NULL);
436 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
437 # ifdef HAVE_HISTORY
438 el_set(_el_el, EL_HIST, &history, _el_hcom);
439 # endif
440 el_set(_el_el, EL_EDITOR, "emacs");
441 # ifdef EL_PROMPT_ESC
442 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
443 # else
444 el_set(_el_el, EL_PROMPT, &_el_getprompt);
445 # endif
446 # if 0
447 el_set(_el_el, EL_ADDFN, "tab_complete",
448 "editline(3) internal completion function", &_el_file_cpl);
449 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
450 # endif
451 # ifdef HAVE_HISTORY
452 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
453 # endif
454 el_source(_el_el, NULL); /* Source ~/.editrc */
456 /* Because el_source() may have introduced yet a different history size
457 * limit, simply load and incorporate the history, leave it up to
458 * editline(3) to do the rest */
459 # ifdef HAVE_HISTORY
460 _CL_HISTFILE(v);
461 if (v != NULL)
462 history(_el_hcom, &he, H_LOAD, v);
463 # endif
466 FL void
467 tty_destroy(void)
469 # ifdef HAVE_HISTORY
470 HistEvent he;
471 char *v;
472 # endif
474 el_end(_el_el);
476 # ifdef HAVE_HISTORY
477 _CL_HISTFILE(v);
478 if (v != NULL)
479 history(_el_hcom, &he, H_SAVE, v);
480 history_end(_el_hcom);
481 # endif
484 FL void
485 tty_signal(int sig)
487 switch (sig) {
488 # ifdef SIGWINCH
489 case SIGWINCH:
490 el_resize(_el_el);
491 break;
492 # endif
493 default:
494 break;
498 FL int
499 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
500 SMALLOC_DEBUG_ARGS)
502 int nn;
503 char const *line;
505 _el_prompt = (prompt != NULL) ? prompt : "";
506 if (n > 0)
507 el_push(_el_el, *linebuf);
508 line = el_gets(_el_el, &nn);
510 if (line == NULL) {
511 nn = -1;
512 goto jleave;
514 assert(nn >= 0);
515 n = (size_t)nn;
516 if (n > 0 && line[n - 1] == '\n')
517 nn = (int)--n;
519 if (n >= *linesize) {
520 *linesize = LINESIZE + n + 1;
521 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
523 memcpy(*linebuf, line, n);
524 (*linebuf)[n] = '\0';
525 jleave:
526 return nn;
529 FL void
530 tty_addhist(char const *s)
532 # ifdef HAVE_HISTORY
533 /* Enlarge meaning of unique .. to something that rocks;
534 * xxx unfortunately this is expensive to do with editline(3)
535 * xxx maybe it would be better to hook the ptfs instead? */
536 HistEvent he;
537 int i;
539 _CL_CHECK_ADDHIST(s, goto jleave);
541 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
542 if (history(_el_hcom, &he, H_GETUNIQUE) < 0 || he.num == 0)
543 goto jadd;
545 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
546 i = history(_el_hcom, &he, H_NEXT))
547 if (strcmp(he.str, s) == 0) {
548 history(_el_hcom, &he, H_DEL, he.num);
549 break;
551 jadd:
552 history(_el_hcom, &he, H_ENTER, s);
553 rele_all_sigs(); /* XXX remove jumps */
554 jleave:
555 # endif
556 UNUSED(s);
559 # ifdef HAVE_HISTORY
560 FL int
561 c_history(void *v)
563 C_HISTORY_SHARED;
565 jlist: {
566 HistEvent he;
567 FILE *fp;
568 char *cp;
569 size_t i, b;
570 int x;
572 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
573 perror("tmpfile");
574 v = NULL;
575 goto jleave;
577 rm(cp);
578 Ftfree(&cp);
580 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
581 b = 0;
582 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
583 x = history(_el_hcom, &he, H_NEXT)) {
584 size_t sl = strlen(he.str);
585 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
586 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
587 --i;
588 b += sl;
591 page_or_print(fp, i);
592 Fclose(fp);
594 goto jleave;
596 jclear: {
597 HistEvent he;
598 history(_el_hcom, &he, H_CLEAR);
600 goto jleave;
602 # endif /* HAVE_HISTORY */
603 #endif /* HAVE_EDITLINE */
606 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
608 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
609 * We do not handle character widths because the terminal must deal with that
610 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
611 * characters by definition on the other. We're addicted.
613 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
614 * we're forced to use the very same buffer--the one that is passed through to
615 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
616 * convert that on-the-fly back to the plain char* result once we're done.
617 * To simplify our live, use savestr() buffers for all other needed memory
621 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
622 * TODO NCL: during handler de-/installation handling.
625 #ifdef HAVE_NCL
626 # ifndef MAX_INPUT
627 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
628 # endif
630 /* Since we simply fputs(3) the prompt, assume each character requires two
631 * visual cells -- and we need to restrict the maximum prompt size because
632 * of MAX_INPUT and our desire to have room for some error message left */
633 # define _PROMPT_VLEN(P) (strlen(P) * 2)
634 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
636 union xsighdl {
637 sighandler_type shdl; /* Try avoid races by setting */
638 sl_it sint; /* .sint=-1 when inactive */
640 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
642 struct xtios {
643 struct termios told;
644 struct termios tnew;
647 struct cell {
648 wchar_t wc;
649 ui_it count;
650 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
653 struct line {
654 size_t cursor; /* Current cursor position */
655 size_t topins; /* Outermost cursor col set */
656 union {
657 char * cbuf; /* *x_buf */
658 struct cell * cells;
659 } line;
660 struct str defc; /* Current default content */
661 struct str savec; /* Saved default content */
662 # ifdef HAVE_HISTORY
663 struct hist * hist; /* History cursor */
664 # endif
665 char const * prompt;
666 char const * nd; /* Cursor right */
667 char ** x_buf; /* Caller pointers */
668 size_t * x_bufsize;
671 # ifdef HAVE_HISTORY
672 struct hist {
673 struct hist * older;
674 struct hist * younger;
675 size_t len;
676 char dat[VFIELD_SIZE(sizeof(size_t))];
678 # endif
680 static union xsighdl _ncl_oint;
681 static union xsighdl _ncl_oquit;
682 static union xsighdl _ncl_oterm;
683 static union xsighdl _ncl_ohup;
684 static union xsighdl _ncl_otstp;
685 static union xsighdl _ncl_ottin;
686 static union xsighdl _ncl_ottou;
687 static struct xtios _ncl_tios;
688 # ifdef HAVE_HISTORY
689 static struct hist * _ncl_hist;
690 static struct hist * _ncl_hist_tail;
691 static size_t _ncl_hist_size;
692 static size_t _ncl_hist_size_max;
693 static bool_t _ncl_hist_load;
694 # endif
696 static void _ncl_sigs_up(void);
697 static void _ncl_sigs_down(void);
699 static void _ncl_term_mode(bool_t raw);
701 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
702 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
703 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
704 static ssize_t _ncl_cell2dat(struct line *l);
705 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
706 static void _ncl_cell2save(struct line *l);
707 # endif
709 static void _ncl_khome(struct line *l, bool_t dobell);
710 static void _ncl_kend(struct line *l);
711 static void _ncl_kbs(struct line *l);
712 static void _ncl_kkill(struct line *l, bool_t dobell);
713 static ssize_t _ncl_keof(struct line *l);
714 static void _ncl_kleft(struct line *l);
715 static void _ncl_kright(struct line *l);
716 static void _ncl_krefresh(struct line *l);
717 static void _ncl_kbwddelw(struct line *l);
718 static void _ncl_kgow(struct line *l, ssize_t dir);
719 static void _ncl_kother(struct line *l, wchar_t wc);
720 # ifdef HAVE_HISTORY
721 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
722 static size_t _ncl_khist(struct line *l, bool_t backwd);
723 static size_t _ncl_krhist(struct line *l);
724 # endif
725 # ifdef HAVE_TABEXPAND
726 static size_t _ncl_kht(struct line *l);
727 # endif
728 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
729 size_t len SMALLOC_DEBUG_ARGS);
731 static void
732 _ncl_sigs_up(void)
734 if (_ncl_oint.sint == -1)
735 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
736 if (_ncl_oquit.sint == -1)
737 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
738 if (_ncl_oterm.sint == -1)
739 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
740 if (_ncl_ohup.sint == -1)
741 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
742 if (_ncl_otstp.sint == -1)
743 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
744 if (_ncl_ottin.sint == -1)
745 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
746 if (_ncl_ottou.sint == -1)
747 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
750 static void
751 _ncl_sigs_down(void)
753 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
754 sighandler_type st;
756 if (_ncl_ottou.sint != -1) {
757 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
758 safe_signal(SIGTTOU, st);
760 if (_ncl_ottin.sint != -1) {
761 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
762 safe_signal(SIGTTIN, st);
764 if (_ncl_otstp.sint != -1) {
765 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
766 safe_signal(SIGTSTP, st);
768 if (_ncl_ohup.sint != -1) {
769 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
770 safe_signal(SIGHUP, st);
772 if (_ncl_oterm.sint != -1) {
773 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
774 safe_signal(SIGTERM, st);
776 if (_ncl_oquit.sint != -1) {
777 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
778 safe_signal(SIGQUIT, st);
780 if (_ncl_oint.sint != -1) {
781 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
782 safe_signal(SIGINT, st);
786 static void
787 _ncl_term_mode(bool_t raw)
789 struct termios *tiosp = &_ncl_tios.told;
791 if (!raw)
792 goto jleave;
794 /* Always requery the attributes, in case we've been moved from background
795 * to foreground or however else in between sessions */
796 tcgetattr(STDIN_FILENO, tiosp);
797 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
798 tiosp = &_ncl_tios.tnew;
799 tiosp->c_cc[VMIN] = 1;
800 tiosp->c_cc[VTIME] = 0;
801 tiosp->c_iflag &= ~(ISTRIP);
802 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
803 jleave:
804 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
807 static void
808 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
810 size_t i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
812 if (i > *l->x_bufsize) {
813 i <<= 1;
814 *l->x_bufsize = i;
815 l->line.cbuf =
816 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
820 static void
821 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
823 size_t j;
825 if (i > 0)
826 memmove(cap, cap + 1, i * sizeof(*cap));
828 /* And.. the (rest of the) visual update */
829 for (j = 0; j < i; ++j)
830 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
831 fputs(" \b", stdout);
832 for (j = 0; j < i; ++j)
833 putchar('\b');
836 static ssize_t
837 _ncl_wboundary(struct line *l, ssize_t dir)
839 size_t c = l->cursor, t = l->topins, i;
840 struct cell *cap;
841 bool_t anynon;
843 i = (size_t)-1;
844 if (dir < 0) {
845 if (c == 0)
846 goto jleave;
847 } else if (c == t)
848 goto jleave;
849 else
850 --t, --c; /* Unsigned wrapping may occur (twice), then */
852 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
853 wchar_t wc = cap[c + dir].wc;
854 if (iswblank(wc) || iswpunct(wc)) {
855 if (anynon)
856 break;
857 } else
858 anynon = TRU1;
859 ++i;
860 c += dir;
861 if (dir < 0) {
862 if (c == 0)
863 break;
864 } else if (c == t)
865 break;
867 jleave:
868 return (ssize_t)i;
871 static ssize_t
872 _ncl_cell2dat(struct line *l)
874 size_t len = 0, i;
876 if (l->topins > 0)
877 for (i = 0; i < l->topins; ++i) {
878 struct cell *cap = l->line.cells + i;
879 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
880 len += cap->count;
882 l->line.cbuf[len] = '\0';
883 return (ssize_t)len;
886 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
887 static void
888 _ncl_cell2save(struct line *l)
890 size_t len, i;
891 struct cell *cap;
893 l->savec.s = NULL, l->savec.l = 0;
894 if (l->topins == 0)
895 goto jleave;
897 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
898 len += cap->count;
900 l->savec.l = len;
901 l->savec.s = salloc(len + 1);
903 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
904 memcpy(l->savec.s + len, cap->cbuf, cap->count);
905 len += cap->count;
907 l->savec.s[len] = '\0';
908 jleave:
911 # endif
913 static void
914 _ncl_khome(struct line *l, bool_t dobell)
916 size_t c = l->cursor;
918 if (c > 0) {
919 l->cursor = 0;
920 while (c-- != 0)
921 putchar('\b');
922 } else if (dobell)
923 putchar('\a');
926 static void
927 _ncl_kend(struct line *l)
929 ssize_t i = (ssize_t)(l->topins - l->cursor);
931 if (i > 0) {
932 l->cursor = l->topins;
933 while (i-- != 0)
934 fputs(l->nd, stdout);
935 } else
936 putchar('\a');
939 static void
940 _ncl_kbs(struct line *l)
942 ssize_t c = l->cursor, t = l->topins;
944 if (c > 0) {
945 putchar('\b');
946 l->cursor = --c;
947 l->topins = --t;
948 t -= c;
949 _ncl_bs_eof_dvup(l->line.cells + c, t);
950 } else
951 putchar('\a');
954 static void
955 _ncl_kkill(struct line *l, bool_t dobell)
957 size_t j, c = l->cursor, i = (size_t)(l->topins - c);
959 if (i > 0) {
960 l->topins = c;
961 for (j = i; j != 0; --j)
962 putchar(' ');
963 for (j = i; j != 0; --j)
964 putchar('\b');
965 } else if (dobell)
966 putchar('\a');
969 static ssize_t
970 _ncl_keof(struct line *l)
972 size_t c = l->cursor, t = l->topins;
973 ssize_t i = (ssize_t)(t - c);
975 if (i > 0) {
976 l->topins = --t;
977 _ncl_bs_eof_dvup(l->line.cells + c, --i);
978 } else if (t == 0 && !ok_blook(ignoreeof)) {
979 fputs("^D", stdout);
980 fflush(stdout);
981 i = -1;
982 } else {
983 putchar('\a');
984 i = 0;
986 return i;
989 static void
990 _ncl_kleft(struct line *l)
992 if (l->cursor > 0) {
993 --l->cursor;
994 putchar('\b');
995 } else
996 putchar('\a');
999 static void
1000 _ncl_kright(struct line *l)
1002 if (l->cursor < l->topins) {
1003 ++l->cursor;
1004 fputs(l->nd, stdout);
1005 } else
1006 putchar('\a');
1009 static void
1010 _ncl_krefresh(struct line *l)
1012 struct cell *cap;
1013 size_t i;
1015 putchar('\r');
1016 if (l->prompt != NULL && *l->prompt != '\0')
1017 fputs(l->prompt, stdout);
1018 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1019 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1020 for (i = l->topins - l->cursor; i > 0; --i)
1021 putchar('\b');
1024 static void
1025 _ncl_kbwddelw(struct line *l)
1027 ssize_t i;
1028 size_t c = l->cursor, t, j;
1029 struct cell *cap;
1031 i = _ncl_wboundary(l, -1);
1032 if (i <= 0) {
1033 if (i < 0)
1034 putchar('\a');
1035 goto jleave;
1038 c = l->cursor - i;
1039 t = l->topins;
1040 l->topins = t - i;
1041 l->cursor = c;
1042 cap = l->line.cells + c;
1044 if (t != l->cursor) {
1045 j = t - c + i;
1046 memmove(cap, cap + i, j * sizeof(*cap));
1049 for (j = i; j > 0; --j)
1050 putchar('\b');
1051 for (j = l->topins - c; j > 0; ++cap, --j)
1052 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1053 for (j = i; j > 0; --j)
1054 putchar(' ');
1055 for (j = t - c; j > 0; --j)
1056 putchar('\b');
1057 jleave:
1061 static void
1062 _ncl_kgow(struct line *l, ssize_t dir)
1064 ssize_t i = _ncl_wboundary(l, dir);
1065 if (i <= 0) {
1066 if (i < 0)
1067 putchar('\a');
1068 goto jleave;
1071 if (dir < 0) {
1072 l->cursor -= i;
1073 while (i-- > 0)
1074 putchar('\b');
1075 } else {
1076 l->cursor += i;
1077 while (i-- > 0)
1078 fputs(l->nd, stdout);
1080 jleave:
1084 static void
1085 _ncl_kother(struct line *l, wchar_t wc)
1087 /* Append if at EOL, insert otherwise;
1088 * since we may move around character-wise, always use a fresh ps */
1089 mbstate_t ps;
1090 struct cell cell, *cap;
1091 size_t i, c;
1093 /* First init a cell and see wether we'll really handle this wc */
1094 cell.wc = wc;
1095 memset(&ps, 0, sizeof ps);
1096 i = wcrtomb(cell.cbuf, wc, &ps);
1097 if (i > MB_LEN_MAX)
1098 goto jleave;
1099 cell.count = (ui_it)i;
1100 if (enc_has_state) {
1101 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1102 if (i == 1)
1104 else if (--i < MB_LEN_MAX)
1105 cell.count += (ui_it)i;
1106 else
1107 goto jleave;
1110 /* Yes, we will! Place it in the array */
1111 c = l->cursor++;
1112 i = l->topins++ - c;
1113 cap = l->line.cells + c;
1114 if (i > 0)
1115 memmove(cap + 1, cap, i * sizeof(cell));
1116 memcpy(cap, &cell, sizeof cell);
1118 /* And update visual */
1119 c = i;
1121 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1122 while ((++cap, i-- != 0));
1123 while (c-- != 0)
1124 putchar('\b');
1125 jleave:
1129 # ifdef HAVE_HISTORY
1130 static size_t
1131 __ncl_khist_shared(struct line *l, struct hist *hp)
1133 size_t rv;
1135 if ((l->hist = hp) != NULL) {
1136 l->defc.s = savestrbuf(hp->dat, hp->len);
1137 rv =
1138 l->defc.l = hp->len;
1139 if (l->topins > 0) {
1140 _ncl_khome(l, FAL0);
1141 _ncl_kkill(l, FAL0);
1143 } else {
1144 putchar('\a');
1145 rv = 0;
1147 return rv;
1150 static size_t
1151 _ncl_khist(struct line *l, bool_t backwd)
1153 struct hist *hp;
1155 /* If we're not in history mode yet, save line content;
1156 * also, disallow forward search, then, and, of course, bail unless we
1157 * do have any history at all */
1158 if ((hp = l->hist) == NULL) {
1159 if (!backwd)
1160 goto jleave;
1161 if ((hp = _ncl_hist) == NULL)
1162 goto jleave;
1163 _ncl_cell2save(l);
1164 goto jleave;
1167 hp = backwd ? hp->older : hp->younger;
1168 jleave:
1169 return __ncl_khist_shared(l, hp);
1172 static size_t
1173 _ncl_krhist(struct line *l)
1175 struct str orig_savec;
1176 struct hist *hp = NULL;
1178 /* We cannot complete an empty line */
1179 if (l->topins == 0) {
1180 /* XXX The upcoming hard reset would restore a set savec buffer,
1181 * XXX so forcefully reset that. A cleaner solution would be to
1182 * XXX reset it whenever a restore is no longer desired */
1183 l->savec.s = NULL, l->savec.l = 0;
1184 goto jleave;
1186 if ((hp = l->hist) == NULL) {
1187 if ((hp = _ncl_hist) == NULL)
1188 goto jleave;
1189 orig_savec.s = NULL;
1190 orig_savec.l = 0; /* silence CC */
1191 } else if ((hp = hp->older) == NULL)
1192 goto jleave;
1193 else
1194 orig_savec = l->savec;
1196 if (orig_savec.s == NULL)
1197 _ncl_cell2save(l);
1198 for (; hp != NULL; hp = hp->older)
1199 if (is_prefix(l->savec.s, hp->dat))
1200 break;
1201 if (orig_savec.s != NULL)
1202 l->savec = orig_savec;
1203 jleave:
1204 return __ncl_khist_shared(l, hp);
1206 # endif
1208 # ifdef HAVE_TABEXPAND
1209 static size_t
1210 _ncl_kht(struct line *l)
1212 struct str orig, bot, topp, sub, exp;
1213 struct cell *cword, *ctop, *cx;
1214 bool_t set_savec = FAL0;
1215 size_t rv = 0;
1217 /* We cannot expand an empty line */
1218 if (l->topins == 0)
1219 goto jleave;
1221 /* Get plain line data; if this is the first expansion/xy, update the
1222 * very original content so that ^G gets the origin back */
1223 orig = l->savec;
1224 _ncl_cell2save(l);
1225 exp = l->savec;
1226 if (orig.s != NULL)
1227 l->savec = orig;
1228 else
1229 set_savec = TRU1;
1230 orig = exp;
1232 cword = l->line.cells;
1233 ctop = cword + l->cursor;
1235 /* topp: separate data right of cursor */
1236 if ((cx = cword + l->topins) != ctop) {
1237 for (rv = 0; cx > ctop; --cx)
1238 rv += cx->count;
1239 topp.l = rv;
1240 topp.s = orig.s + orig.l - rv;
1241 } else
1242 topp.s = NULL, topp.l = 0;
1244 /* bot, sub: we cannot expand the entire data left of cursor, but only
1245 * the last "word", so separate them */
1246 while (cx > cword && !iswspace(cx[-1].wc))
1247 --cx;
1248 for (rv = 0; cword < cx; ++cword)
1249 rv += cword->count;
1250 sub =
1251 bot = orig;
1252 bot.l = rv;
1253 sub.s += rv;
1254 sub.l -= rv;
1255 sub.l -= topp.l;
1257 if (sub.l > 0) {
1258 sub.s = savestrbuf(sub.s, sub.l);
1259 /* TODO there is a TODO note upon fexpand() with multi-return;
1260 * TODO if that will change, the if() below can be simplified */
1261 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1262 hold_all_sigs();
1263 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1264 rele_all_sigs();
1266 if (exp.s != NULL && (exp.l = strlen(exp.s)) > 0 &&
1267 (exp.l != sub.l || strcmp(exp.s, sub.s))) {
1268 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1269 * Take care to take *prompt* into account, since we don't know
1270 * anything about it's visual length (fputs(3) is used), simply
1271 * assume each character requires two columns */
1272 /* TODO the problem is that we loose control otherwise; in the best
1273 * TODO case the user can control via ^A and ^K etc., but be safe;
1274 * TODO we cannot simply adjust fexpand() because we don't know how
1275 * TODO that is implemented... The real solution would be to check
1276 * TODO wether we fit on a line, and start a pager if not.
1277 * TODO However, that should be part of a real tab-COMPLETION, then,
1278 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1279 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1280 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1281 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1282 char const e1[] = "[maximum line size exceeded]";
1283 exp.s = UNCONST(e1);
1284 exp.l = sizeof(e1) - 1;
1285 topp.l = 0;
1286 if (rv + bot.l + exp.l >= MAX_INPUT)
1287 bot.l = 0;
1288 if (rv + exp.l >= MAX_INPUT) {
1289 char const e2[] = "[ERR]";
1290 exp.s = UNCONST(e2);
1291 exp.l = sizeof(e2) - 1;
1294 orig.l = bot.l + exp.l + topp.l;
1295 orig.s = salloc(orig.l + 1 + 5);
1296 if ((rv = bot.l) > 0)
1297 memcpy(orig.s, bot.s, rv);
1298 memcpy(orig.s + rv, exp.s, exp.l);
1299 rv += exp.l;
1300 if (topp.l > 0) {
1301 memcpy(orig.s + rv, topp.s, topp.l);
1302 rv += topp.l;
1304 orig.s[rv] = '\0';
1306 l->defc = orig;
1307 _ncl_khome(l, FAL0);
1308 _ncl_kkill(l, FAL0);
1309 goto jleave;
1313 /* If we've provided a default content, but failed to expand, there is
1314 * nothing we can "revert to": drop that default again */
1315 if (set_savec)
1316 l->savec.s = NULL, l->savec.l = 0;
1317 rv = 0;
1318 jleave:
1319 return rv;
1321 # endif /* HAVE_TABEXPAND */
1323 static ssize_t
1324 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1325 SMALLOC_DEBUG_ARGS)
1327 /* We want to save code, yet we may have to incorporate a lines'
1328 * default content and / or default input to switch back to after some
1329 * history movement; let "len > 0" mean "have to display some data
1330 * buffer", and only otherwise read(2) it */
1331 mbstate_t ps[2];
1332 struct line l;
1333 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
1334 wchar_t wc;
1335 ssize_t rv;
1336 ui32_t maybe_cursor;
1338 memset(&l, 0, sizeof l);
1339 l.line.cbuf = *buf;
1340 if (len != 0) {
1341 l.defc.s = savestrbuf(*buf, len);
1342 l.defc.l = len;
1344 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1345 l.prompt = prompt = "?ERR?";
1346 /* TODO *l.nd=='\0' only because we have no value-cache -> see acmava.c */
1347 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1348 l.nd = "\033[C"; /* XXX no "magic" constant */
1349 l.x_buf = buf;
1350 l.x_bufsize = bufsize;
1352 if (prompt != NULL && *prompt != '\0') {
1353 fputs(prompt, stdout);
1354 fflush(stdout);
1356 jrestart:
1357 memset(ps, 0, sizeof ps);
1358 maybe_cursor = 0;
1359 /* TODO: NCL: we should output the reset sequence when we jrestart:
1360 * TODO: NCL: if we are using a stateful encoding? !
1361 * TODO: NCL: in short: this is not yet well understood */
1362 for (;;) {
1363 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1365 /* Normal read(2)? Else buffer-takeover: speed this one up */
1366 if (len == 0)
1367 cbufp =
1368 cbuf = cbuf_base;
1369 else {
1370 assert(l.defc.l > 0 && l.defc.s != NULL);
1371 cbufp =
1372 cbuf = l.defc.s + (l.defc.l - len);
1373 cbufp += len;
1376 /* Read in the next complete multibyte character */
1377 for (;;) {
1378 if (len == 0) {
1379 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1380 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1381 continue;
1382 goto jleave;
1384 ++cbufp;
1387 /* Ach! the ISO C multibyte handling!
1388 * Encodings with locking shift states cannot really be helped, since
1389 * it is impossible to only query the shift state, as opposed to the
1390 * entire shift state + character pair (via ISO C functions) */
1391 rv = (ssize_t)mbrtowc(&wc, cbuf, (size_t)(cbufp - cbuf), ps + 0);
1392 if (rv <= 0) {
1393 /* Any error during take-over can only result in a hard reset;
1394 * Otherwise, if it's a hard error, or if too many redundant shift
1395 * sequences overflow our buffer, also perform a hard reset */
1396 if (len != 0 || rv == -1 ||
1397 sizeof cbuf_base == (size_t)(cbufp - cbuf)) {
1398 l.savec.s = l.defc.s = NULL,
1399 l.savec.l = l.defc.l = len = 0;
1400 putchar('\a');
1401 wc = 'G';
1402 goto jreset;
1404 /* Otherwise, due to the way we deal with the buffer, we need to
1405 * restore the mbstate_t from before this conversion */
1406 ps[0] = ps[1];
1407 continue;
1410 if (len != 0 && (len -= (size_t)rv) == 0)
1411 l.defc.s = NULL, l.defc.l = 0;
1412 ps[1] = ps[0];
1413 break;
1416 /* Don't interpret control bytes during buffer take-over */
1417 if (cbuf != cbuf_base)
1418 goto jprint;
1419 switch (wc) {
1420 case 'A' ^ 0x40: /* cursor home */
1421 _ncl_khome(&l, TRU1);
1422 break;
1423 j_b:
1424 case 'B' ^ 0x40: /* backward character */
1425 _ncl_kleft(&l);
1426 break;
1427 /* 'C': interrupt (CTRL-C) */
1428 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1429 if ((rv = _ncl_keof(&l)) < 0)
1430 goto jleave;
1431 break;
1432 case 'E' ^ 0x40: /* end of line */
1433 _ncl_kend(&l);
1434 break;
1435 j_f:
1436 case 'F' ^ 0x40: /* forward character */
1437 _ncl_kright(&l);
1438 break;
1439 /* 'G' below */
1440 case 'H' ^ 0x40: /* backspace */
1441 case '\177':
1442 _ncl_kbs(&l);
1443 break;
1444 case 'I' ^ 0x40: /* horizontal tab */
1445 # ifdef HAVE_TABEXPAND
1446 if ((len = _ncl_kht(&l)) > 0)
1447 goto jrestart;
1448 # endif
1449 goto jbell;
1450 case 'J' ^ 0x40: /* NL (\n) */
1451 goto jdone;
1452 case 'G' ^ 0x40: /* full reset */
1453 jreset:
1454 /* FALLTHRU */
1455 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1456 _ncl_khome(&l, FAL0);
1457 /* FALLTHRU */
1458 case 'K' ^ 0x40: /* kill from cursor to end of line */
1459 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1460 /* (Handle full reset?) */
1461 if (wc == ('G' ^ 0x40)) {
1462 # ifdef HAVE_HISTORY
1463 l.hist = NULL;
1464 # endif
1465 if ((len = l.savec.l) != 0) {
1466 l.defc = l.savec;
1467 l.savec.s = NULL, l.savec.l = 0;
1468 } else
1469 len = l.defc.l;
1471 fflush(stdout);
1472 goto jrestart;
1473 case 'L' ^ 0x40: /* repaint line */
1474 _ncl_krefresh(&l);
1475 break;
1476 /* 'M': CR (\r) */
1477 j_n:
1478 case 'N' ^ 0x40: /* history next */
1479 # ifdef HAVE_HISTORY
1480 if (l.hist == NULL)
1481 goto jbell;
1482 if ((len = _ncl_khist(&l, FAL0)) > 0)
1483 goto jrestart;
1484 wc = 'G' ^ 0x40;
1485 goto jreset;
1486 # else
1487 goto jbell;
1488 # endif
1489 /* 'O' */
1490 j_p:
1491 case 'P' ^ 0x40: /* history previous */
1492 # ifdef HAVE_HISTORY
1493 if ((len = _ncl_khist(&l, TRU1)) > 0)
1494 goto jrestart;
1495 wc = 'G' ^ 0x40;
1496 goto jreset;
1497 # else
1498 goto jbell;
1499 # endif
1500 /* 'Q': no code */
1501 case 'R' ^ 0x40: /* reverse history search */
1502 # ifdef HAVE_HISTORY
1503 if ((len = _ncl_krhist(&l)) > 0)
1504 goto jrestart;
1505 wc = 'G' ^ 0x40;
1506 goto jreset;
1507 # else
1508 goto jbell;
1509 # endif
1510 /* 'S': no code */
1511 /* 'U' above */
1512 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1513 case 'W' ^ 0x40: /* backward delete "word" */
1514 _ncl_kbwddelw(&l);
1515 break;
1516 case 'X' ^ 0x40: /* move cursor forward "word" */
1517 _ncl_kgow(&l, +1);
1518 break;
1519 case 'Y' ^ 0x40: /* move cursor backward "word" */
1520 _ncl_kgow(&l, -1);
1521 break;
1522 /* 'Z': suspend (CTRL-Z) */
1523 case 0x1B:
1524 if (maybe_cursor++ != 0)
1525 goto jreset;
1526 continue;
1527 default:
1528 /* XXX Handle usual ^[[[ABCD] cursor keys -- UGLY, "MAGIC", INFLEX */
1529 if (maybe_cursor > 0) {
1530 if (++maybe_cursor == 2) {
1531 if (wc == L'[')
1532 continue;
1533 maybe_cursor = 0;
1534 } else {
1535 maybe_cursor = 0;
1536 switch (wc) {
1537 case L'A': goto j_p;
1538 case L'B': goto j_n;
1539 case L'C': goto j_f;
1540 case L'D': goto j_b;
1542 _ncl_kother(&l, L'[');
1545 jprint:
1546 if (iswprint(wc)) {
1547 _ncl_kother(&l, wc);
1548 /* Don't clear the history during takeover..
1549 * ..and also avoid fflush()ing unless we've
1550 * worked the entire buffer */
1551 if (len > 0)
1552 continue;
1553 # ifdef HAVE_HISTORY
1554 if (cbuf == cbuf_base)
1555 l.hist = NULL;
1556 # endif
1557 } else {
1558 jbell:
1559 putchar('\a');
1561 break;
1563 fflush(stdout);
1566 /* We have a completed input line, convert the struct cell data to its
1567 * plain character equivalent */
1568 jdone:
1569 putchar('\n');
1570 fflush(stdout);
1571 len = _ncl_cell2dat(&l);
1572 rv = (ssize_t)len;
1573 jleave:
1574 return rv;
1577 FL void
1578 tty_init(void)
1580 # ifdef HAVE_HISTORY
1581 long hs;
1582 char *v, *lbuf;
1583 FILE *f;
1584 size_t lsize, cnt, llen;
1585 # endif
1587 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1588 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1589 _ncl_ottou.sint = -1;
1591 # ifdef HAVE_HISTORY
1592 _CL_HISTSIZE(hs);
1593 _ncl_hist_size = 0;
1594 _ncl_hist_size_max = hs;
1595 if (hs == 0)
1596 goto jleave;
1598 _CL_HISTFILE(v);
1599 if (v == NULL)
1600 goto jleave;
1602 hold_all_sigs(); /* XXX too heavy, yet we may jump even here!? */
1603 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1604 if (f == NULL)
1605 goto jdone;
1607 lbuf = NULL;
1608 lsize = 0;
1609 cnt = fsize(f);
1610 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1611 if (llen > 0 && lbuf[llen - 1] == '\n')
1612 lbuf[--llen] = '\0';
1613 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1614 continue;
1615 _ncl_hist_load = TRU1;
1616 tty_addhist(lbuf);
1617 _ncl_hist_load = FAL0;
1619 if (lbuf != NULL)
1620 free(lbuf);
1622 fclose(f);
1623 jdone:
1624 rele_all_sigs(); /* XXX remove jumps */
1625 jleave:
1626 # endif /* HAVE_HISTORY */
1630 FL void
1631 tty_destroy(void)
1633 # ifdef HAVE_HISTORY
1634 long hs;
1635 char *v;
1636 struct hist *hp;
1637 FILE *f;
1639 _CL_HISTSIZE(hs);
1640 if (hs == 0)
1641 goto jleave;
1643 _CL_HISTFILE(v);
1644 if (v == NULL)
1645 goto jleave;
1647 if ((hp = _ncl_hist) != NULL)
1648 while (hp->older != NULL && hs-- != 0)
1649 hp = hp->older;
1651 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1652 f = fopen(v, "w"); /* TODO temporary + rename?! */
1653 if (f == NULL)
1654 goto jdone;
1655 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1656 goto jclose;
1658 for (; hp != NULL; hp = hp->younger) {
1659 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1660 putc('\n', f);
1662 jclose:
1663 fclose(f);
1664 jdone:
1665 rele_all_sigs(); /* XXX remove jumps */
1666 jleave:
1667 # endif /* HAVE_HISTORY */
1671 FL void
1672 tty_signal(int sig)
1674 sigset_t nset, oset;
1676 switch (sig) {
1677 case SIGWINCH:
1678 /* We don't deal with SIGWINCH, yet get called from main.c */
1679 break;
1680 default:
1681 _ncl_term_mode(FAL0);
1682 _ncl_sigs_down();
1683 sigemptyset(&nset);
1684 sigaddset(&nset, sig);
1685 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1686 kill(0, sig);
1687 /* When we come here we'll continue editing, so reestablish */
1688 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1689 _ncl_sigs_up();
1690 _ncl_term_mode(TRU1);
1691 break;
1695 FL int
1696 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1697 SMALLOC_DEBUG_ARGS)
1699 ssize_t nn;
1701 /* Of course we have races here, but they cannot be avoided on POSIX
1702 * (except by even *more* actions) */
1703 _ncl_sigs_up();
1704 _ncl_term_mode(TRU1);
1705 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1706 _ncl_term_mode(FAL0);
1707 _ncl_sigs_down();
1709 return (int)nn;
1712 FL void
1713 tty_addhist(char const *s)
1715 # ifdef HAVE_HISTORY
1716 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1717 size_t l = strlen(s);
1718 struct hist *h, *o, *y;
1720 if (_ncl_hist_size_max == 0)
1721 goto j_leave;
1722 _CL_CHECK_ADDHIST(s, goto j_leave);
1724 /* Eliminating duplicates is expensive, but simply inacceptable so
1725 * during the load of a potentially large history file! */
1726 if (!_ncl_hist_load)
1727 for (h = _ncl_hist; h != NULL; h = h->older)
1728 if (h->len == l && !strcmp(h->dat, s)) {
1729 hold_all_sigs(); /* TODO */
1730 o = h->older;
1731 y = h->younger;
1732 if (o != NULL)
1733 o->younger = y;
1734 else
1735 _ncl_hist_tail = y;
1736 if (y != NULL)
1737 y->older = o;
1738 else
1739 _ncl_hist = o;
1740 goto jleave;
1742 hold_all_sigs();
1744 ++_ncl_hist_size;
1745 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1746 --_ncl_hist_size;
1747 if ((h = _ncl_hist_tail) != NULL) {
1748 if ((_ncl_hist_tail = h->younger) == NULL)
1749 _ncl_hist = NULL;
1750 else
1751 _ncl_hist_tail->older = NULL;
1752 free(h);
1756 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1757 h->len = l;
1758 memcpy(h->dat, s, l +1);
1759 jleave:
1760 if ((h->older = _ncl_hist) != NULL)
1761 _ncl_hist->younger = h;
1762 else
1763 _ncl_hist_tail = h;
1764 h->younger = NULL;
1765 _ncl_hist = h;
1767 rele_all_sigs();
1768 j_leave:
1769 # endif
1770 UNUSED(s);
1773 # ifdef HAVE_HISTORY
1774 FL int
1775 c_history(void *v)
1777 C_HISTORY_SHARED;
1779 jlist: {
1780 FILE *fp;
1781 char *cp;
1782 size_t i, b;
1783 struct hist *h;
1785 if (_ncl_hist == NULL)
1786 goto jleave;
1788 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1789 perror("tmpfile");
1790 v = NULL;
1791 goto jleave;
1793 rm(cp);
1794 Ftfree(&cp);
1796 i = _ncl_hist_size;
1797 b = 0;
1798 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
1799 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
1800 (ul_it)i, h->dat, (ul_it)b, (ul_it)h->len);
1802 page_or_print(fp, i);
1803 Fclose(fp);
1805 goto jleave;
1807 jclear: {
1808 struct hist *h;
1809 while ((h = _ncl_hist) != NULL) {
1810 _ncl_hist = h->older;
1811 free(h);
1813 _ncl_hist_tail = NULL;
1814 _ncl_hist_size = 0;
1816 goto jleave;
1818 # endif /* HAVE_HISTORY */
1819 #endif /* HAVE_NCL */
1822 * The really-nothing-at-all implementation
1825 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
1826 FL void
1827 tty_init(void)
1830 FL void
1831 tty_destroy(void)
1834 FL void
1835 tty_signal(int sig)
1837 UNUSED(sig);
1840 FL int
1841 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1842 SMALLOC_DEBUG_ARGS)
1845 * TODO The nothing-at-all tty layer even forces re-entering all the
1846 * TODO original data when re-editing a field
1848 bool_t doffl = FAL0;
1850 if (prompt != NULL && *prompt != '\0') {
1851 fputs(prompt, stdout);
1852 doffl = TRU1;
1854 if (n > 0) {
1855 fprintf(stdout, tr(511, "{former content: %.*s} "), (int)n, *linebuf);
1856 n = 0;
1857 doffl = TRU1;
1859 if (doffl)
1860 fflush(stdout);
1861 return (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
1864 FL void
1865 tty_addhist(char const *s)
1867 UNUSED(s);
1869 #endif /* nothing at all */
1871 /* vim:set fenc=utf-8:s-it-mode */