.netrc: support comments (Walter Alejandro Iglesias, Ralph Corderoy)..
[s-mailx.git] / tty.c
blob7677ffc04ac08513fb198af87c88613db47ac2e7
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ TTY interaction.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /* The NCL version is
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 * Copyright (c) 1980, 1993
22 * The Regents of the University of California. All rights reserved.
24 * Redistribution and use in source and binary forms, with or without
25 * modification, are permitted provided that the following conditions
26 * are met:
27 * 1. Redistributions of source code must retain the above copyright
28 * notice, this list of conditions and the following disclaimer.
29 * 2. Redistributions in binary form must reproduce the above copyright
30 * notice, this list of conditions and the following disclaimer in the
31 * documentation and/or other materials provided with the distribution.
32 * 3. Neither the name of the University nor the names of its contributors
33 * may be used to endorse or promote products derived from this software
34 * without specific prior written permission.
36 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
37 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
40 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
41 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
42 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
44 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
45 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46 * SUCH DAMAGE.
48 #undef n_FILE
49 #define n_FILE tty
51 #ifndef HAVE_AMALGAMATION
52 # include "nail.h"
53 #endif
55 #ifdef HAVE_READLINE
56 # include <readline/readline.h>
57 # ifdef HAVE_HISTORY
58 # include <readline/history.h>
59 # endif
60 #elif defined HAVE_EDITLINE
61 # include <histedit.h>
62 #endif
64 /* Shared history support macros */
65 #ifdef HAVE_HISTORY
66 # define _CL_HISTFILE(S) \
67 do {\
68 char const *__hist_obsolete = ok_vlook(NAIL_HISTFILE);\
69 if (__hist_obsolete != NULL)\
70 OBSOLETE(_("please use *history-file* instead of *NAIL_HISTFILE*"));\
71 S = ok_vlook(history_file);\
72 if ((S) == NULL)\
73 (S) = __hist_obsolete;\
74 if ((S) != NULL)\
75 S = fexpand(S, FEXP_LOCAL | FEXP_NSHELL);\
76 } while(0)
78 # define _CL_HISTSIZE(V) \
79 do {\
80 char const *__hist_obsolete = ok_vlook(NAIL_HISTSIZE);\
81 char const *__sv = ok_vlook(history_size);\
82 long __rv;\
83 if (__hist_obsolete != NULL)\
84 OBSOLETE(_("please use *history-size* instead of *NAIL_HISTSIZE*"));\
85 if (__sv == NULL)\
86 __sv = __hist_obsolete;\
87 if (__sv == NULL || *__sv == '\0' || (__rv = strtol(__sv, NULL, 10)) == 0)\
88 (V) = HIST_SIZE;\
89 else if (__rv < 0)\
90 (V) = 0;\
91 else\
92 (V) = __rv;\
93 } while (0)
95 # define _CL_CHECK_ADDHIST(S,NOACT) \
96 do {\
97 switch (*(S)) {\
98 case '\0':\
99 case ' ':\
100 NOACT;\
101 default:\
102 break;\
104 } while (0)
106 # define C_HISTORY_SHARED \
107 char **argv = v;\
108 long entry;\
109 NYD_ENTER;\
111 if (*argv == NULL)\
112 goto jlist;\
113 if (argv[1] != NULL)\
114 goto jerr;\
115 if (!asccasecmp(*argv, "show"))\
116 goto jlist;\
117 if (!asccasecmp(*argv, "clear"))\
118 goto jclear;\
119 if ((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
120 goto jentry;\
121 jerr:\
122 n_err(_("Synopsis: history: %s\n" \
123 "<show> (default), <clear> or select <NO> from editor history"));\
124 v = NULL;\
125 jleave:\
126 NYD_LEAVE;\
127 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
128 #endif /* HAVE_HISTORY */
130 /* fexpand() flags for expand-on-tab */
131 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
134 * Because we have multiple identical implementations, change file layout a bit
135 * and place the implementations one after the other below the other externals
138 static sigjmp_buf __n_tty_actjmp; /* TODO someday, we won't need it no more */
139 static void
140 __n_tty_acthdl(int s) /* TODO someday, we won't need it no more */
142 NYD_X; /* Signal handler */
143 termios_state_reset();
144 siglongjmp(__n_tty_actjmp, s);
147 FL bool_t
148 getapproval(char const * volatile prompt, bool_t noninteract_default)
150 sighandler_type volatile oint, ohup;
151 bool_t volatile rv;
152 int volatile sig;
153 NYD_ENTER;
155 if (!(options & OPT_INTERACTIVE)) {
156 sig = 0;
157 rv = noninteract_default;
158 goto jleave;
160 rv = FAL0;
162 /* C99 */{
163 char const *quest = noninteract_default
164 ? _("[yes]/no? ") : _("[no]/yes? ");
166 if (prompt == NULL)
167 prompt = _("Continue");
168 prompt = savecatsep(prompt, ' ', quest);
171 oint = safe_signal(SIGINT, SIG_IGN);
172 ohup = safe_signal(SIGHUP, SIG_IGN);
173 if ((sig = sigsetjmp(__n_tty_actjmp, 1)) != 0)
174 goto jrestore;
175 safe_signal(SIGINT, &__n_tty_acthdl);
176 safe_signal(SIGHUP, &__n_tty_acthdl);
178 if (readline_input(prompt, FAL0, &termios_state.ts_linebuf,
179 &termios_state.ts_linesize, NULL) >= 0)
180 rv = (boolify(termios_state.ts_linebuf, UIZ_MAX,
181 noninteract_default) > 0);
182 jrestore:
183 termios_state_reset();
185 safe_signal(SIGHUP, ohup);
186 safe_signal(SIGINT, oint);
187 jleave:
188 NYD_LEAVE;
189 if (sig != 0)
190 n_raise(sig);
191 return rv;
194 #ifdef HAVE_SOCKETS
195 FL char *
196 getuser(char const * volatile query) /* TODO v15-compat obsolete */
198 sighandler_type volatile oint, ohup;
199 char * volatile user = NULL;
200 int volatile sig;
201 NYD_ENTER;
203 if (query == NULL)
204 query = _("User: ");
206 oint = safe_signal(SIGINT, SIG_IGN);
207 ohup = safe_signal(SIGHUP, SIG_IGN);
208 if ((sig = sigsetjmp(__n_tty_actjmp, 1)) != 0)
209 goto jrestore;
210 safe_signal(SIGINT, &__n_tty_acthdl);
211 safe_signal(SIGHUP, &__n_tty_acthdl);
213 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
214 &termios_state.ts_linesize, NULL) >= 0)
215 user = termios_state.ts_linebuf;
216 jrestore:
217 termios_state_reset();
219 safe_signal(SIGHUP, ohup);
220 safe_signal(SIGINT, oint);
221 NYD_LEAVE;
222 if (sig != 0)
223 n_raise(sig);
224 return user;
227 FL char *
228 getpassword(char const *query)
230 sighandler_type volatile oint, ohup;
231 struct termios tios;
232 char * volatile pass = NULL;
233 int volatile sig;
234 NYD_ENTER;
236 if (query == NULL)
237 query = _("Password: ");
238 fputs(query, stdout);
239 fflush(stdout);
241 /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
242 * FIXME foreground pgrp, and can fail with EINTR!! also affects
243 * FIXME termios_state_reset() */
244 if (options & OPT_TTYIN) {
245 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
246 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
247 termios_state.ts_needs_reset = TRU1;
248 tios.c_iflag &= ~(ISTRIP);
249 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
252 oint = safe_signal(SIGINT, SIG_IGN);
253 ohup = safe_signal(SIGHUP, SIG_IGN);
254 if ((sig = sigsetjmp(__n_tty_actjmp, 1)) != 0)
255 goto jrestore;
256 safe_signal(SIGINT, &__n_tty_acthdl);
257 safe_signal(SIGHUP, &__n_tty_acthdl);
259 if (options & OPT_TTYIN)
260 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
262 if (readline_restart(stdin, &termios_state.ts_linebuf,
263 &termios_state.ts_linesize, 0) >= 0)
264 pass = termios_state.ts_linebuf;
265 jrestore:
266 termios_state_reset();
267 if (options & OPT_TTYIN)
268 putc('\n', stdout);
270 safe_signal(SIGHUP, ohup);
271 safe_signal(SIGINT, oint);
272 NYD_LEAVE;
273 if (sig != 0)
274 n_raise(sig);
275 return pass;
277 #endif /* HAVE_SOCKETS */
280 * readline(3)
283 #ifdef HAVE_READLINE
284 static sighandler_type _rl_shup;
285 static char * _rl_buf; /* pre_input() hook: initial line */
286 static int _rl_buflen; /* content, and its length */
288 static int _rl_pre_input(void);
290 static int
291 _rl_pre_input(void)
293 NYD_ENTER;
294 /* Handle leftover data from \ escaped former line */
295 rl_extend_line_buffer(_rl_buflen + 10);
296 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
297 rl_point = rl_end = _rl_buflen;
298 rl_pre_input_hook = (rl_hook_func_t*)NULL;
299 rl_redisplay();
300 NYD_LEAVE;
301 return 0;
304 FL void
305 n_tty_init(void)
307 # ifdef HAVE_HISTORY
308 long hs;
309 char const *v;
310 # endif
311 NYD_ENTER;
313 rl_readline_name = UNCONST(uagent);
314 # ifdef HAVE_HISTORY
315 _CL_HISTSIZE(hs);
316 using_history();
317 stifle_history((int)hs);
318 # endif
319 rl_read_init_file(NULL);
321 /* Because rl_read_init_file() may have introduced yet a different
322 * history size limit, simply load and incorporate the history, leave
323 * it up to readline(3) to do the rest */
324 # ifdef HAVE_HISTORY
325 _CL_HISTFILE(v);
326 if (v != NULL)
327 read_history(v);
328 # endif
329 NYD_LEAVE;
332 FL void
333 n_tty_destroy(void)
335 # ifdef HAVE_HISTORY
336 char const *v;
337 # endif
338 NYD_ENTER;
340 # ifdef HAVE_HISTORY
341 _CL_HISTFILE(v);
342 if (v != NULL)
343 write_history(v);
344 # endif
345 NYD_LEAVE;
348 FL void
349 n_tty_signal(int sig)
351 sigset_t nset, oset;
352 NYD_X; /* Signal handler */
354 switch (sig) {
355 # ifdef SIGWINCH
356 case SIGWINCH:
357 break;
358 # endif
359 case SIGHUP:
360 /* readline(3) doesn't catch it :( */
361 rl_free_line_state();
362 rl_cleanup_after_signal();
363 safe_signal(SIGHUP, _rl_shup);
364 sigemptyset(&nset);
365 sigaddset(&nset, sig);
366 sigprocmask(SIG_UNBLOCK, &nset, &oset);
367 n_raise(sig);
368 /* XXX When we come here we'll continue editing, so reestablish
369 * XXX cannot happen */
370 sigprocmask(SIG_BLOCK, &oset, NULL);
371 _rl_shup = safe_signal(SIGHUP, &n_tty_signal);
372 rl_reset_after_signal();
373 break;
374 default:
375 break;
379 FL int
380 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
381 SMALLOC_DEBUG_ARGS)
383 int nn;
384 char *line;
385 NYD_ENTER;
387 if (n > 0) {
388 _rl_buf = *linebuf;
389 _rl_buflen = (int)n;
390 rl_pre_input_hook = &_rl_pre_input;
393 _rl_shup = safe_signal(SIGHUP, &n_tty_signal);
394 line = readline(prompt != NULL ? prompt : "");
395 safe_signal(SIGHUP, _rl_shup);
397 if (line == NULL) {
398 nn = -1;
399 goto jleave;
401 n = strlen(line);
403 if (n >= *linesize) {
404 *linesize = LINESIZE + n +1;
405 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
407 memcpy(*linebuf, line, n);
408 (free)(line);
409 (*linebuf)[n] = '\0';
410 nn = (int)n;
411 jleave:
412 NYD_LEAVE;
413 return nn;
416 FL void
417 n_tty_addhist(char const *s, bool_t isgabby)
419 NYD_ENTER;
420 UNUSED(s);
421 UNUSED(isgabby);
422 # ifdef HAVE_HISTORY
423 if (isgabby && !ok_blook(history_gabby))
424 goto jleave;
425 _CL_CHECK_ADDHIST(s, goto jleave);
426 hold_all_sigs(); /* XXX too heavy */
427 add_history(s); /* XXX yet we jump away! */
428 rele_all_sigs(); /* XXX remove jumps */
429 jleave:
430 # endif
431 NYD_LEAVE;
434 # ifdef HAVE_HISTORY
435 FL int
436 c_history(void *v)
438 C_HISTORY_SHARED;
440 jlist: {
441 FILE *fp;
442 HISTORY_STATE *hs;
443 HIST_ENTRY **hl;
444 ul_i i, b;
446 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
447 n_perr(_("tmpfile"), 0);
448 v = NULL;
449 goto jleave;
452 hs = history_get_history_state();
454 for (i = (ul_i)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
455 char *cp = (*--hl)->line;
456 size_t sl = strlen(cp);
457 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n", i, cp, b, sl);
458 b += sl;
461 page_or_print(fp, (size_t)hs->length);
462 Fclose(fp);
464 goto jleave;
466 jclear:
467 clear_history();
468 goto jleave;
470 jentry: {
471 HISTORY_STATE *hs = history_get_history_state();
473 if (UICMP(z, entry, <=, hs->length))
474 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
475 else
476 v = NULL;
478 goto jleave;
480 # endif /* HAVE_HISTORY */
481 #endif /* HAVE_READLINE */
484 * BSD editline(3)
487 #ifdef HAVE_EDITLINE
488 static EditLine * _el_el; /* editline(3) handle */
489 static char const * _el_prompt; /* Current prompt */
490 # ifdef HAVE_HISTORY
491 static History * _el_hcom; /* History handle for commline */
492 # endif
494 static char const * _el_getprompt(void);
496 static char const *
497 _el_getprompt(void)
499 return _el_prompt;
502 FL void
503 n_tty_init(void)
505 # ifdef HAVE_HISTORY
506 HistEvent he;
507 long hs;
508 char const *v;
509 # endif
510 NYD_ENTER;
512 # ifdef HAVE_HISTORY
513 _CL_HISTSIZE(hs);
514 _el_hcom = history_init();
515 history(_el_hcom, &he, H_SETSIZE, (int)hs);
516 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
517 # endif
519 _el_el = el_init(uagent, stdin, stdout, stderr);
520 el_set(_el_el, EL_SIGNAL, 1);
521 el_set(_el_el, EL_TERMINAL, NULL);
522 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
523 # ifdef HAVE_HISTORY
524 el_set(_el_el, EL_HIST, &history, _el_hcom);
525 # endif
526 el_set(_el_el, EL_EDITOR, "emacs");
527 # ifdef EL_PROMPT_ESC
528 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
529 # else
530 el_set(_el_el, EL_PROMPT, &_el_getprompt);
531 # endif
532 # if 0
533 el_set(_el_el, EL_ADDFN, "tab_complete",
534 "editline(3) internal completion function", &_el_file_cpl);
535 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
536 # endif
537 # ifdef HAVE_HISTORY
538 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
539 # endif
540 el_source(_el_el, NULL); /* Source ~/.editrc */
542 NYD;
543 /* Because el_source() may have introduced yet a different history size
544 * limit, simply load and incorporate the history, leave it up to
545 * editline(3) to do the rest */
546 # ifdef HAVE_HISTORY
547 _CL_HISTFILE(v);
548 if (v != NULL)
549 history(_el_hcom, &he, H_LOAD, v);
550 # endif
551 NYD;
552 NYD_LEAVE;
555 FL void
556 n_tty_destroy(void)
558 # ifdef HAVE_HISTORY
559 HistEvent he;
560 char const *v;
561 # endif
562 NYD_ENTER;
564 el_end(_el_el);
566 # ifdef HAVE_HISTORY
567 _CL_HISTFILE(v);
568 if (v != NULL)
569 history(_el_hcom, &he, H_SAVE, v);
570 history_end(_el_hcom);
571 # endif
572 NYD_LEAVE;
575 FL void
576 n_tty_signal(int sig)
578 NYD_X; /* Signal handler */
579 switch (sig) {
580 # ifdef SIGWINCH
581 case SIGWINCH:
582 el_resize(_el_el);
583 break;
584 # endif
585 default:
586 break;
590 FL int
591 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
592 SMALLOC_DEBUG_ARGS)
594 int nn;
595 char const *line;
596 NYD_ENTER;
598 _el_prompt = (prompt != NULL) ? prompt : "";
599 if (n > 0)
600 el_push(_el_el, *linebuf);
601 line = el_gets(_el_el, &nn);
603 if (line == NULL) {
604 nn = -1;
605 goto jleave;
607 assert(nn >= 0);
608 n = (size_t)nn;
609 if (n > 0 && line[n - 1] == '\n')
610 nn = (int)--n;
612 if (n >= *linesize) {
613 *linesize = LINESIZE + n + 1;
614 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
616 memcpy(*linebuf, line, n);
617 (*linebuf)[n] = '\0';
618 jleave:
619 NYD_LEAVE;
620 return nn;
623 FL void
624 n_tty_addhist(char const *s, bool_t isgabby)
626 # ifdef HAVE_HISTORY
627 /* Enlarge meaning of unique .. to something that rocks;
628 * xxx unfortunately this is expensive to do with editline(3)
629 * xxx maybe it would be better to hook the ptfs instead? */
630 HistEvent he;
631 int i;
632 # endif
633 NYD_ENTER;
634 UNUSED(s);
635 UNUSED(isgabby);
637 # ifdef HAVE_HISTORY
638 if (isgabby && !ok_blook(history_gabby))
639 goto jleave;
640 _CL_CHECK_ADDHIST(s, goto jleave);
642 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
643 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
644 i = history(_el_hcom, &he, H_NEXT))
645 if (!strcmp(he.str, s)) {
646 history(_el_hcom, &he, H_DEL, he.num);
647 break;
649 history(_el_hcom, &he, H_ENTER, s);
650 rele_all_sigs(); /* XXX remove jumps */
651 jleave:
652 # endif
653 NYD_LEAVE;
656 # ifdef HAVE_HISTORY
657 FL int
658 c_history(void *v)
660 C_HISTORY_SHARED;
662 jlist: {
663 HistEvent he;
664 FILE *fp;
665 size_t i, b;
666 int x;
668 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
669 n_perr(_("tmpfile"), 0);
670 v = NULL;
671 goto jleave;
674 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
675 b = 0;
676 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
677 x = history(_el_hcom, &he, H_NEXT)) {
678 size_t sl = strlen(he.str);
679 fprintf(fp, "%4lu. %-50.50s (%4lu+%2lu bytes)\n",
680 (ul_i)i, he.str, (ul_i)b, (ul_i)sl);
681 --i;
682 b += sl;
685 page_or_print(fp, i);
686 Fclose(fp);
688 goto jleave;
690 jclear: {
691 HistEvent he;
692 history(_el_hcom, &he, H_CLEAR);
694 goto jleave;
696 jentry: {
697 HistEvent he;
698 size_t i;
699 int x;
701 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
702 if (UICMP(z, entry, <=, i)) {
703 entry = (long)i - entry;
704 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
705 x = history(_el_hcom, &he, H_NEXT))
706 if (entry-- == 0) {
707 v = temporary_arg_v_store = UNCONST(he.str);
708 goto jleave;
711 v = NULL;
713 goto jleave;
715 # endif /* HAVE_HISTORY */
716 #endif /* HAVE_EDITLINE */
719 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
721 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
722 * We do not handle character widths because the terminal must deal with that
723 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
724 * characters by definition on the other. We're addicted.
726 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
727 * we're forced to use the very same buffer--the one that is passed through to
728 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
729 * convert that on-the-fly back to the plain char* result once we're done.
730 * To simplify our live, use savestr() buffers for all other needed memory
733 #ifdef HAVE_NCL
734 # ifndef MAX_INPUT
735 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
736 # endif
738 /* Since we simply fputs(3) the prompt, assume each character requires two
739 * visual cells -- and we need to restrict the maximum prompt size because
740 * of MAX_INPUT and our desire to have room for some error message left */
741 # define _PROMPT_VLEN(P) (strlen(P) * 2)
742 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
744 struct xtios {
745 struct termios told;
746 struct termios tnew;
749 struct cell {
750 wchar_t wc;
751 ui32_t count;
752 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
755 struct line {
756 size_t cursor; /* Current cursor position */
757 size_t topins; /* Outermost cursor col set */
758 union {
759 char *cbuf; /* *x_buf */
760 struct cell *cells;
761 } line;
762 struct str defc; /* Current default content */
763 struct str savec; /* Saved default content */
764 # ifdef HAVE_HISTORY
765 struct hist *hist; /* History cursor */
766 # endif
767 char const *prompt;
768 char const *nd; /* Cursor right */
769 char **x_buf; /* Caller pointers */
770 size_t *x_bufsize;
773 # ifdef HAVE_HISTORY
774 struct hist {
775 struct hist *older;
776 struct hist *younger;
777 ui32_t isgabby : 1;
778 ui32_t len : 31;
779 char dat[VFIELD_SIZE(sizeof(ui32_t))];
781 # endif
783 static sighandler_type _ncl_oint;
784 static sighandler_type _ncl_oquit;
785 static sighandler_type _ncl_oterm;
786 static sighandler_type _ncl_ohup;
787 static sighandler_type _ncl_otstp;
788 static sighandler_type _ncl_ottin;
789 static sighandler_type _ncl_ottou;
790 static struct xtios _ncl_tios;
791 # ifdef HAVE_HISTORY
792 static struct hist *_ncl_hist;
793 static struct hist *_ncl_hist_tail;
794 static size_t _ncl_hist_size;
795 static size_t _ncl_hist_size_max;
796 static bool_t _ncl_hist_load;
797 # endif
799 static void _ncl_sigs_up(void);
800 static void _ncl_sigs_down(void);
802 static void _ncl_term_mode(bool_t raw);
804 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
805 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
806 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
807 static ssize_t _ncl_cell2dat(struct line *l);
808 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
809 static void _ncl_cell2save(struct line *l);
810 # endif
812 static void _ncl_khome(struct line *l, bool_t dobell);
813 static void _ncl_kend(struct line *l);
814 static void _ncl_kbs(struct line *l);
815 static void _ncl_kkill(struct line *l, bool_t dobell);
816 static ssize_t _ncl_keof(struct line *l);
817 static void _ncl_kleft(struct line *l);
818 static void _ncl_kright(struct line *l);
819 static void _ncl_krefresh(struct line *l);
820 static void _ncl_kbwddelw(struct line *l);
821 static void _ncl_kgow(struct line *l, ssize_t dir);
822 static void _ncl_kother(struct line *l, wchar_t wc);
823 # ifdef HAVE_HISTORY
824 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
825 static size_t _ncl_khist(struct line *l, bool_t backwd);
826 static size_t _ncl_krhist(struct line *l);
827 # endif
828 # ifdef HAVE_TABEXPAND
829 static size_t _ncl_kht(struct line *l);
830 # endif
831 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
832 size_t len SMALLOC_DEBUG_ARGS);
834 static void
835 _ncl_sigs_up(void)
837 sigset_t nset, oset;
838 NYD2_ENTER;
840 sigfillset(&nset);
842 sigprocmask(SIG_BLOCK, &nset, &oset);
843 _ncl_oint = safe_signal(SIGINT, &n_tty_signal);
844 _ncl_oquit = safe_signal(SIGQUIT, &n_tty_signal);
845 _ncl_oterm = safe_signal(SIGTERM, &n_tty_signal);
846 _ncl_ohup = safe_signal(SIGHUP, &n_tty_signal);
847 _ncl_otstp = safe_signal(SIGTSTP, &n_tty_signal);
848 _ncl_ottin = safe_signal(SIGTTIN, &n_tty_signal);
849 _ncl_ottou = safe_signal(SIGTTOU, &n_tty_signal);
850 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
851 NYD2_LEAVE;
854 static void
855 _ncl_sigs_down(void)
857 sigset_t nset, oset;
858 NYD2_ENTER;
860 sigfillset(&nset);
862 sigprocmask(SIG_BLOCK, &nset, &oset);
863 safe_signal(SIGINT, _ncl_oint);
864 safe_signal(SIGQUIT, _ncl_oquit);
865 safe_signal(SIGTERM, _ncl_oterm);
866 safe_signal(SIGHUP, _ncl_ohup);
867 safe_signal(SIGTSTP, _ncl_otstp);
868 safe_signal(SIGTTIN, _ncl_ottin);
869 safe_signal(SIGTTOU, _ncl_ottou);
870 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
871 NYD2_LEAVE;
874 static void
875 _ncl_term_mode(bool_t raw)
877 struct termios *tiosp;
878 NYD2_ENTER;
880 tiosp = &_ncl_tios.told;
881 if (!raw)
882 goto jleave;
884 /* Always requery the attributes, in case we've been moved from background
885 * to foreground or however else in between sessions */
886 /* XXX Always enforce ECHO and ICANON in the OLD attributes - do so as long
887 * XXX as we don't properly deal with TTIN and TTOU etc. */
888 tcgetattr(STDIN_FILENO, tiosp);
889 tiosp->c_lflag |= ECHO | ICANON;
891 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
892 tiosp = &_ncl_tios.tnew;
893 tiosp->c_cc[VMIN] = 1;
894 tiosp->c_cc[VTIME] = 0;
895 tiosp->c_iflag &= ~(ISTRIP);
896 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
897 jleave:
898 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
899 NYD2_LEAVE;
902 static void
903 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
905 size_t i;
906 NYD2_ENTER;
908 i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
909 if (i >= *l->x_bufsize) {
910 i <<= 1;
911 *l->x_bufsize = i;
912 l->line.cbuf =
913 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
915 NYD2_LEAVE;
918 static void
919 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
921 size_t j;
922 NYD2_ENTER;
924 if (i > 0)
925 memmove(cap, cap + 1, i * sizeof(*cap));
927 /* And.. the (rest of the) visual update */
928 for (j = 0; j < i; ++j)
929 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
930 fputs(" \b", stdout);
931 for (j = 0; j < i; ++j)
932 putchar('\b');
933 NYD2_LEAVE;
936 static ssize_t
937 _ncl_wboundary(struct line *l, ssize_t dir)
939 size_t c, t, i;
940 struct cell *cap;
941 bool_t anynon;
942 NYD2_ENTER;
944 c = l->cursor;
945 t = l->topins;
946 i = (size_t)-1;
948 if (dir < 0) {
949 if (c == 0)
950 goto jleave;
951 } else if (c == t)
952 goto jleave;
953 else
954 --t, --c; /* Unsigned wrapping may occur (twice), then */
956 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
957 wchar_t wc = cap[c + dir].wc;
958 if (iswblank(wc) || iswpunct(wc)) {
959 if (anynon)
960 break;
961 } else
962 anynon = TRU1;
963 ++i;
964 c += dir;
965 if (dir < 0) {
966 if (c == 0)
967 break;
968 } else if (c == t)
969 break;
971 jleave:
972 NYD2_LEAVE;
973 return (ssize_t)i;
976 static ssize_t
977 _ncl_cell2dat(struct line *l)
979 size_t len = 0, i;
980 NYD2_ENTER;
982 if (l->topins > 0)
983 for (i = 0; i < l->topins; ++i) {
984 struct cell *cap = l->line.cells + i;
985 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
986 len += cap->count;
988 l->line.cbuf[len] = '\0';
989 NYD2_LEAVE;
990 return (ssize_t)len;
993 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
994 static void
995 _ncl_cell2save(struct line *l)
997 size_t len, i;
998 struct cell *cap;
999 NYD2_ENTER;
1001 l->savec.s = NULL, l->savec.l = 0;
1002 if (l->topins == 0)
1003 goto jleave;
1005 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
1006 len += cap->count;
1008 l->savec.l = len;
1009 l->savec.s = salloc(len + 1);
1011 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
1012 memcpy(l->savec.s + len, cap->cbuf, cap->count);
1013 len += cap->count;
1015 l->savec.s[len] = '\0';
1016 jleave:
1017 NYD2_LEAVE;
1019 # endif
1021 static void
1022 _ncl_khome(struct line *l, bool_t dobell)
1024 size_t c;
1025 NYD2_ENTER;
1027 c = l->cursor;
1028 if (c > 0) {
1029 l->cursor = 0;
1030 while (c-- != 0)
1031 putchar('\b');
1032 } else if (dobell)
1033 putchar('\a');
1034 NYD2_LEAVE;
1037 static void
1038 _ncl_kend(struct line *l)
1040 ssize_t i;
1041 NYD2_ENTER;
1043 i = (ssize_t)(l->topins - l->cursor);
1045 if (i > 0) {
1046 l->cursor = l->topins;
1047 while (i-- != 0)
1048 fputs(l->nd, stdout);
1049 } else
1050 putchar('\a');
1051 NYD2_LEAVE;
1054 static void
1055 _ncl_kbs(struct line *l)
1057 ssize_t c, t;
1058 NYD2_ENTER;
1060 c = l->cursor;
1061 t = l->topins;
1063 if (c > 0) {
1064 putchar('\b');
1065 l->cursor = --c;
1066 l->topins = --t;
1067 t -= c;
1068 _ncl_bs_eof_dvup(l->line.cells + c, t);
1069 } else
1070 putchar('\a');
1071 NYD2_LEAVE;
1074 static void
1075 _ncl_kkill(struct line *l, bool_t dobell)
1077 size_t j, c, i;
1078 NYD2_ENTER;
1080 c = l->cursor;
1081 i = (size_t)(l->topins - c);
1083 if (i > 0) {
1084 l->topins = c;
1085 for (j = i; j != 0; --j)
1086 putchar(' ');
1087 for (j = i; j != 0; --j)
1088 putchar('\b');
1089 } else if (dobell)
1090 putchar('\a');
1091 NYD2_LEAVE;
1094 static ssize_t
1095 _ncl_keof(struct line *l)
1097 size_t c, t;
1098 ssize_t i;
1099 NYD2_ENTER;
1101 c = l->cursor;
1102 t = l->topins;
1103 i = (ssize_t)(t - c);
1105 if (i > 0) {
1106 l->topins = --t;
1107 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1108 } else if (t == 0 /*&& !ok_blook(ignoreeof)*/) {
1109 /*fputs("^D", stdout);
1110 fflush(stdout);*/
1111 i = -1;
1112 /*} else {
1113 putchar('\a');
1114 i = 0;*/
1116 NYD2_LEAVE;
1117 return i;
1120 static void
1121 _ncl_kleft(struct line *l)
1123 NYD2_ENTER;
1124 if (l->cursor > 0) {
1125 --l->cursor;
1126 putchar('\b');
1127 } else
1128 putchar('\a');
1129 NYD2_LEAVE;
1132 static void
1133 _ncl_kright(struct line *l)
1135 NYD2_ENTER;
1136 if (l->cursor < l->topins) {
1137 ++l->cursor;
1138 fputs(l->nd, stdout);
1139 } else
1140 putchar('\a');
1141 NYD2_LEAVE;
1144 static void
1145 _ncl_krefresh(struct line *l)
1147 struct cell *cap;
1148 size_t i;
1149 NYD2_ENTER;
1151 putchar('\r');
1152 if (l->prompt != NULL && *l->prompt != '\0')
1153 fputs(l->prompt, stdout);
1154 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1155 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1156 for (i = l->topins - l->cursor; i > 0; --i)
1157 putchar('\b');
1158 NYD2_LEAVE;
1161 static void
1162 _ncl_kbwddelw(struct line *l)
1164 ssize_t i;
1165 size_t c, t, j;
1166 struct cell *cap;
1167 NYD2_ENTER;
1169 i = _ncl_wboundary(l, -1);
1170 if (i <= 0) {
1171 if (i < 0)
1172 putchar('\a');
1173 goto jleave;
1176 c = l->cursor - i;
1177 t = l->topins;
1178 l->topins = t - i;
1179 l->cursor = c;
1180 cap = l->line.cells + c;
1182 if (t != l->cursor) {
1183 j = t - c + i;
1184 memmove(cap, cap + i, j * sizeof(*cap));
1187 for (j = i; j > 0; --j)
1188 putchar('\b');
1189 for (j = l->topins - c; j > 0; ++cap, --j)
1190 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1191 for (j = i; j > 0; --j)
1192 putchar(' ');
1193 for (j = t - c; j > 0; --j)
1194 putchar('\b');
1195 jleave:
1196 NYD2_LEAVE;
1199 static void
1200 _ncl_kgow(struct line *l, ssize_t dir)
1202 ssize_t i;
1203 NYD2_ENTER;
1205 i = _ncl_wboundary(l, dir);
1206 if (i <= 0) {
1207 if (i < 0)
1208 putchar('\a');
1209 goto jleave;
1212 if (dir < 0) {
1213 l->cursor -= i;
1214 while (i-- > 0)
1215 putchar('\b');
1216 } else {
1217 l->cursor += i;
1218 while (i-- > 0)
1219 fputs(l->nd, stdout);
1221 jleave:
1222 NYD2_LEAVE;
1225 static void
1226 _ncl_kother(struct line *l, wchar_t wc)
1228 /* Append if at EOL, insert otherwise;
1229 * since we may move around character-wise, always use a fresh ps */
1230 mbstate_t ps;
1231 struct cell cell, *cap;
1232 size_t i, c;
1233 NYD2_ENTER;
1235 /* First init a cell and see wether we'll really handle this wc */
1236 cell.wc = wc;
1237 memset(&ps, 0, sizeof ps);
1238 i = wcrtomb(cell.cbuf, wc, &ps);
1239 if (i > MB_LEN_MAX)
1240 goto jleave;
1241 cell.count = (ui32_t)i;
1242 if (options & OPT_ENC_MBSTATE) {
1243 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1244 if (i == 1)
1246 else if (--i < MB_LEN_MAX)
1247 cell.count += (ui32_t)i;
1248 else
1249 goto jleave;
1252 /* Yes, we will! Place it in the array */
1253 c = l->cursor++;
1254 i = l->topins++ - c;
1255 cap = l->line.cells + c;
1256 if (i > 0)
1257 memmove(cap + 1, cap, i * sizeof(cell));
1258 memcpy(cap, &cell, sizeof cell);
1260 /* And update visual */
1261 c = i;
1263 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1264 while ((++cap, i-- != 0));
1265 while (c-- != 0)
1266 putchar('\b');
1267 jleave:
1268 NYD2_LEAVE;
1271 # ifdef HAVE_HISTORY
1272 static size_t
1273 __ncl_khist_shared(struct line *l, struct hist *hp)
1275 size_t rv;
1276 NYD2_ENTER;
1278 if ((l->hist = hp) != NULL) {
1279 l->defc.s = savestrbuf(hp->dat, hp->len);
1280 rv =
1281 l->defc.l = hp->len;
1282 if (l->topins > 0) {
1283 _ncl_khome(l, FAL0);
1284 _ncl_kkill(l, FAL0);
1286 } else {
1287 putchar('\a');
1288 rv = 0;
1290 NYD2_LEAVE;
1291 return rv;
1294 static size_t
1295 _ncl_khist(struct line *l, bool_t backwd)
1297 struct hist *hp;
1298 size_t rv;
1299 NYD2_ENTER;
1301 /* If we're not in history mode yet, save line content;
1302 * also, disallow forward search, then, and, of course, bail unless we
1303 * do have any history at all */
1304 if ((hp = l->hist) == NULL) {
1305 if (!backwd)
1306 goto jleave;
1307 if ((hp = _ncl_hist) == NULL)
1308 goto jleave;
1309 _ncl_cell2save(l);
1310 goto jleave;
1313 hp = backwd ? hp->older : hp->younger;
1314 jleave:
1315 rv = __ncl_khist_shared(l, hp);
1316 NYD2_LEAVE;
1317 return rv;
1320 static size_t
1321 _ncl_krhist(struct line *l)
1323 struct str orig_savec;
1324 struct hist *hp = NULL;
1325 size_t rv;
1326 NYD2_ENTER;
1328 /* We cannot complete an empty line */
1329 if (l->topins == 0) {
1330 /* XXX The upcoming hard reset would restore a set savec buffer,
1331 * XXX so forcefully reset that. A cleaner solution would be to
1332 * XXX reset it whenever a restore is no longer desired */
1333 l->savec.s = NULL, l->savec.l = 0;
1334 goto jleave;
1336 if ((hp = l->hist) == NULL) {
1337 if ((hp = _ncl_hist) == NULL)
1338 goto jleave;
1339 orig_savec.s = NULL;
1340 orig_savec.l = 0; /* silence CC */
1341 } else if ((hp = hp->older) == NULL)
1342 goto jleave;
1343 else
1344 orig_savec = l->savec;
1346 if (orig_savec.s == NULL)
1347 _ncl_cell2save(l);
1348 for (; hp != NULL; hp = hp->older)
1349 if (is_prefix(l->savec.s, hp->dat))
1350 break;
1351 if (orig_savec.s != NULL)
1352 l->savec = orig_savec;
1353 jleave:
1354 rv = __ncl_khist_shared(l, hp);
1355 NYD2_LEAVE;
1356 return rv;
1358 # endif
1360 # ifdef HAVE_TABEXPAND
1361 static size_t
1362 _ncl_kht(struct line *l)
1364 struct str orig, bot, topp, sub, exp;
1365 struct cell *cword, *ctop, *cx;
1366 bool_t set_savec = FAL0;
1367 size_t rv = 0;
1368 NYD2_ENTER;
1370 /* We cannot expand an empty line */
1371 if (l->topins == 0)
1372 goto jleave;
1374 /* Get plain line data; if this is the first expansion/xy, update the
1375 * very original content so that ^G gets the origin back */
1376 orig = l->savec;
1377 _ncl_cell2save(l);
1378 exp = l->savec;
1379 if (orig.s != NULL)
1380 l->savec = orig;
1381 else
1382 set_savec = TRU1;
1383 orig = exp;
1385 cword = l->line.cells;
1386 ctop = cword + l->cursor;
1388 /* topp: separate data right of cursor */
1389 if ((cx = cword + l->topins) != ctop) {
1390 for (rv = 0; cx > ctop; --cx)
1391 rv += cx->count;
1392 topp.l = rv;
1393 topp.s = orig.s + orig.l - rv;
1394 } else
1395 topp.s = NULL, topp.l = 0;
1397 /* bot, sub: we cannot expand the entire data left of cursor, but only
1398 * the last "word", so separate them */
1399 while (cx > cword && !iswspace(cx[-1].wc))
1400 --cx;
1401 for (rv = 0; cword < cx; ++cword)
1402 rv += cword->count;
1403 sub =
1404 bot = orig;
1405 bot.l = rv;
1406 sub.s += rv;
1407 sub.l -= rv;
1408 sub.l -= topp.l;
1410 /* Leave room for "implicit asterisk" expansion, as below */
1411 if (sub.l == 0) {
1412 sub.s = UNCONST("*");
1413 sub.l = 1;
1414 } else {
1415 exp.s = salloc(sub.l + 1 +1);
1416 memcpy(exp.s, sub.s, sub.l);
1417 exp.s[sub.l] = '\0';
1418 sub.s = exp.s;
1421 /* TODO there is a TODO note upon fexpand() with multi-return;
1422 * TODO if that will change, the if() below can be simplified */
1423 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1424 jredo:
1425 hold_all_sigs();
1426 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1427 rele_all_sigs();
1429 if (exp.s == NULL || (exp.l = strlen(exp.s)) == 0)
1430 goto jnope;
1431 /* If the expansion equals the original string, assume the user wants what
1432 * is usually known as tab completion, append `*' and restart */
1433 if (exp.l == sub.l && !strcmp(exp.s, sub.s)) {
1434 if (sub.s[sub.l - 1] == '*')
1435 goto jnope;
1436 sub.s[sub.l++] = '*';
1437 sub.s[sub.l] = '\0';
1438 goto jredo;
1441 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1442 * Take care to take *prompt* into account, since we don't know
1443 * anything about it's visual length (fputs(3) is used), simply
1444 * assume each character requires two columns */
1445 /* TODO the problem is that we loose control otherwise; in the best
1446 * TODO case the user can control via ^A and ^K etc., but be safe;
1447 * TODO we cannot simply adjust fexpand() because we don't know how
1448 * TODO that is implemented... The real solution would be to check
1449 * TODO wether we fit on a line, and start a pager if not.
1450 * TODO However, that should be part of a real tab-COMPLETION, then,
1451 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1452 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1453 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1454 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1455 exp.s = UNCONST("[ERR_TOO_LONG]");
1456 exp.l = sizeof("[ERR_TOO_LONG]") - 1;
1457 topp.l = 0;
1458 if (rv + bot.l + exp.l >= MAX_INPUT)
1459 bot.l = 0;
1460 if (rv + exp.l >= MAX_INPUT) {
1461 exp.s = UNCONST("[ERR]");
1462 exp.l = sizeof("[ERR]") - 1;
1466 orig.l = bot.l + exp.l + topp.l;
1467 orig.s = salloc(orig.l + 5 +1);
1468 if ((rv = bot.l) > 0)
1469 memcpy(orig.s, bot.s, rv);
1470 memcpy(orig.s + rv, exp.s, exp.l);
1471 rv += exp.l;
1472 if (topp.l > 0) {
1473 memcpy(orig.s + rv, topp.s, topp.l);
1474 rv += topp.l;
1476 orig.s[rv] = '\0';
1478 l->defc = orig;
1479 _ncl_khome(l, FAL0);
1480 _ncl_kkill(l, FAL0);
1481 jleave:
1482 NYD2_LEAVE;
1483 return rv;
1484 jnope:
1485 /* If we've provided a default content, but failed to expand, there is
1486 * nothing we can "revert to": drop that default again */
1487 if (set_savec)
1488 l->savec.s = NULL, l->savec.l = 0;
1489 rv = 0;
1490 goto jleave;
1492 # endif /* HAVE_TABEXPAND */
1494 static ssize_t
1495 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1496 SMALLOC_DEBUG_ARGS)
1498 /* We want to save code, yet we may have to incorporate a lines'
1499 * default content and / or default input to switch back to after some
1500 * history movement; let "len > 0" mean "have to display some data
1501 * buffer", and only otherwise read(2) it */
1502 mbstate_t ps[2];
1503 struct line l;
1504 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp, cursor_maybe, cursor_store;
1505 wchar_t wc;
1506 ssize_t rv;
1507 NYD_ENTER;
1509 memset(&l, 0, sizeof l);
1510 l.line.cbuf = *buf;
1511 if (len != 0) {
1512 l.defc.s = savestrbuf(*buf, len);
1513 l.defc.l = len;
1515 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1516 l.prompt = prompt = "?ERR?";
1517 /* TODO *l.nd=='\0' : instead adjust accmacvar.c to disallow empty vals */
1518 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1519 l.nd = "\033[C"; /* XXX no "magic" constant */
1520 l.x_buf = buf;
1521 l.x_bufsize = bufsize;
1523 if (prompt != NULL && *prompt != '\0')
1524 fputs(prompt, stdout);
1525 fflush(stdout);
1527 jrestart:
1528 memset(ps, 0, sizeof ps);
1529 cursor_maybe = cursor_store = 0;
1530 /* TODO: NCL: we should output the reset sequence when we jrestart:
1531 * TODO: NCL: if we are using a stateful encoding? !
1532 * TODO: NCL: in short: this is not yet well understood */
1533 for (;;) {
1534 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1536 /* Normal read(2)? Else buffer-takeover: speed this one up */
1537 if (len == 0)
1538 cbufp =
1539 cbuf = cbuf_base;
1540 else {
1541 assert(l.defc.l > 0 && l.defc.s != NULL);
1542 cbufp =
1543 cbuf = l.defc.s + (l.defc.l - len);
1544 cbufp += len;
1547 /* Read in the next complete multibyte character */
1548 for (;;) {
1549 if (len == 0) {
1550 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1551 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1552 continue;
1553 goto jleave;
1555 ++cbufp;
1558 /* Ach! the ISO C multibyte handling!
1559 * Encodings with locking shift states cannot really be helped, since
1560 * it is impossible to only query the shift state, as opposed to the
1561 * entire shift state + character pair (via ISO C functions) */
1562 rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), ps + 0);
1563 if (rv <= 0) {
1564 /* Any error during take-over can only result in a hard reset;
1565 * Otherwise, if it's a hard error, or if too many redundant shift
1566 * sequences overflow our buffer, also perform a hard reset */
1567 if (len != 0 || rv == -1 ||
1568 sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)) {
1569 l.savec.s = l.defc.s = NULL,
1570 l.savec.l = l.defc.l = len = 0;
1571 putchar('\a');
1572 wc = 'G';
1573 goto jreset;
1575 /* Otherwise, due to the way we deal with the buffer, we need to
1576 * restore the mbstate_t from before this conversion */
1577 ps[0] = ps[1];
1578 continue;
1581 if (len != 0 && (len -= (size_t)rv) == 0)
1582 l.defc.s = NULL, l.defc.l = 0;
1583 ps[1] = ps[0];
1584 break;
1587 /* Don't interpret control bytes during buffer take-over */
1588 if (cbuf != cbuf_base)
1589 goto jprint;
1590 switch (wc) {
1591 case 'A' ^ 0x40: /* cursor home */
1592 _ncl_khome(&l, TRU1);
1593 break;
1594 case 'B' ^ 0x40: /* backward character */
1595 j_b:
1596 _ncl_kleft(&l);
1597 break;
1598 /* 'C': interrupt (CTRL-C) */
1599 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1600 if ((rv = _ncl_keof(&l)) < 0)
1601 goto jleave;
1602 break;
1603 case 'E' ^ 0x40: /* end of line */
1604 _ncl_kend(&l);
1605 break;
1606 case 'F' ^ 0x40: /* forward character */
1607 j_f:
1608 _ncl_kright(&l);
1609 break;
1610 /* 'G' below */
1611 case 'H' ^ 0x40: /* backspace */
1612 case '\177':
1613 _ncl_kbs(&l);
1614 break;
1615 case 'I' ^ 0x40: /* horizontal tab */
1616 # ifdef HAVE_TABEXPAND
1617 if ((len = _ncl_kht(&l)) > 0)
1618 goto jrestart;
1619 # endif
1620 goto jbell;
1621 case 'J' ^ 0x40: /* NL (\n) */
1622 goto jdone;
1623 case 'G' ^ 0x40: /* full reset */
1624 jreset:
1625 /* FALLTHRU */
1626 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1627 _ncl_khome(&l, FAL0);
1628 /* FALLTHRU */
1629 case 'K' ^ 0x40: /* kill from cursor to end of line */
1630 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1631 /* (Handle full reset?) */
1632 if (wc == ('G' ^ 0x40)) {
1633 # ifdef HAVE_HISTORY
1634 l.hist = NULL;
1635 # endif
1636 if ((len = l.savec.l) != 0) {
1637 l.defc = l.savec;
1638 l.savec.s = NULL, l.savec.l = 0;
1639 } else
1640 len = l.defc.l;
1642 fflush(stdout);
1643 goto jrestart;
1644 case 'L' ^ 0x40: /* repaint line */
1645 j_l:
1646 _ncl_krefresh(&l);
1647 break;
1648 /* 'M': CR (\r) */
1649 case 'N' ^ 0x40: /* history next */
1650 j_n:
1651 # ifdef HAVE_HISTORY
1652 if (l.hist == NULL)
1653 goto jbell;
1654 if ((len = _ncl_khist(&l, FAL0)) > 0)
1655 goto jrestart;
1656 wc = 'G' ^ 0x40;
1657 goto jreset;
1658 # else
1659 goto jbell;
1660 # endif
1661 /* 'O' */
1662 case 'O' ^ 0x40: /* `dp' */
1663 putchar('\n');
1664 cbuf_base[0] = 'd';
1665 cbuf_base[1] = 'p';
1666 cbuf_base[2] = '\0';
1667 pstate &= ~PS_HOOK_MASK;
1668 execute(cbuf_base, 2);
1669 goto j_l;
1670 case 'P' ^ 0x40: /* history previous */
1671 j_p:
1672 # ifdef HAVE_HISTORY
1673 if ((len = _ncl_khist(&l, TRU1)) > 0)
1674 goto jrestart;
1675 wc = 'G' ^ 0x40;
1676 goto jreset;
1677 # else
1678 goto jbell;
1679 # endif
1680 /* 'Q': no code */
1681 case 'R' ^ 0x40: /* reverse history search */
1682 # ifdef HAVE_HISTORY
1683 if ((len = _ncl_krhist(&l)) > 0)
1684 goto jrestart;
1685 wc = 'G' ^ 0x40;
1686 goto jreset;
1687 # else
1688 goto jbell;
1689 # endif
1690 /* 'S': no code */
1691 /* 'U' above */
1692 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1693 case 'W' ^ 0x40: /* backward delete "word" */
1694 _ncl_kbwddelw(&l);
1695 break;
1696 case 'X' ^ 0x40: /* move cursor forward "word" */
1697 _ncl_kgow(&l, +1);
1698 break;
1699 case 'Y' ^ 0x40: /* move cursor backward "word" */
1700 _ncl_kgow(&l, -1);
1701 break;
1702 /* 'Z': suspend (CTRL-Z) */
1703 case 0x1B:
1704 if (cursor_maybe++ != 0)
1705 goto jreset;
1706 continue;
1707 default:
1708 /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */
1709 if (cursor_maybe > 0) {
1710 if (++cursor_maybe == 2) {
1711 if (wc == L'[')
1712 continue;
1713 cursor_maybe = 0;
1714 } else if (cursor_maybe == 3) {
1715 cursor_maybe = 0;
1716 switch (wc) {
1717 default: break;
1718 case L'A': goto j_p;
1719 case L'B': goto j_n;
1720 case L'C': goto j_f;
1721 case L'D': goto j_b;
1722 case L'H':
1723 cursor_store = '0';
1724 goto J_xterm_noapp;
1725 case L'F':
1726 cursor_store = '$';
1727 goto J_xterm_noapp;
1728 case L'1':
1729 case L'4':
1730 case L'5':
1731 case L'6':
1732 cursor_store = ((wc == L'1') ? '0' :
1733 (wc == L'4' ? '$' : (wc == L'5' ? '-' : '+')));
1734 cursor_maybe = 3;
1735 continue;
1737 _ncl_kother(&l, L'[');
1738 } else {
1739 cursor_maybe = 0;
1740 if (wc == L'~')
1741 J_xterm_noapp: {
1742 char x[2];
1743 x[0] = cursor_store;
1744 x[1] = '\0';
1745 putchar('\n');
1746 c_scroll(x);
1747 cursor_store = 0;
1748 goto j_l;
1749 } else if (cursor_store == '-' && (wc == L'A' || wc == L'B')) {
1750 char x[2];
1751 x[0] = (wc != L'A') ? '+' : cursor_store;
1752 x[1] = '\0';
1753 putchar('\n');
1754 c_dotmove(x);
1755 cursor_store = 0;
1756 goto j_l;
1758 _ncl_kother(&l, L'[');
1759 _ncl_kother(&l, (wchar_t)cursor_store);
1760 cursor_store = 0;
1763 jprint:
1764 if (iswprint(wc)) {
1765 _ncl_kother(&l, wc);
1766 /* Don't clear the history during takeover..
1767 * ..and also avoid fflush()ing unless we've worked entire buffer */
1768 if (len > 0)
1769 continue;
1770 # ifdef HAVE_HISTORY
1771 if (cbuf == cbuf_base)
1772 l.hist = NULL;
1773 # endif
1774 } else {
1775 jbell:
1776 putchar('\a');
1778 break;
1780 fflush(stdout);
1783 /* We have a completed input line, convert the struct cell data to its
1784 * plain character equivalent */
1785 jdone:
1786 putchar('\n');
1787 fflush(stdout);
1788 len = _ncl_cell2dat(&l);
1789 rv = (ssize_t)len;
1790 jleave:
1791 NYD_LEAVE;
1792 return rv;
1795 FL void
1796 n_tty_init(void)
1798 # ifdef HAVE_HISTORY
1799 long hs;
1800 char const *v;
1801 char *lbuf;
1802 FILE *f;
1803 size_t lsize, cnt, llen;
1804 # endif
1805 NYD_ENTER;
1807 # ifdef HAVE_HISTORY
1808 _CL_HISTSIZE(hs);
1809 _ncl_hist_size = 0;
1810 _ncl_hist_size_max = hs;
1811 if (hs == 0)
1812 goto jleave;
1814 _CL_HISTFILE(v);
1815 if (v == NULL)
1816 goto jleave;
1818 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1819 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1820 if (f == NULL)
1821 goto jdone;
1822 (void)file_lock(fileno(f), FLT_READ, 0,0, 500);
1824 lbuf = NULL;
1825 lsize = 0;
1826 cnt = fsize(f);
1827 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1828 if (llen > 0 && lbuf[llen - 1] == '\n')
1829 lbuf[--llen] = '\0';
1830 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1831 continue;
1832 else {
1833 bool_t isgabby = (lbuf[0] == '*');
1834 _ncl_hist_load = TRU1;
1835 n_tty_addhist(lbuf + isgabby, isgabby);
1836 _ncl_hist_load = FAL0;
1839 if (lbuf != NULL)
1840 free(lbuf);
1842 fclose(f);
1843 jdone:
1844 rele_all_sigs(); /* XXX remove jumps */
1845 jleave:
1846 # endif /* HAVE_HISTORY */
1847 NYD_LEAVE;
1850 FL void
1851 n_tty_destroy(void)
1853 # ifdef HAVE_HISTORY
1854 long hs;
1855 char const *v;
1856 struct hist *hp;
1857 bool_t dogabby;
1858 FILE *f;
1859 # endif
1860 NYD_ENTER;
1862 # ifdef HAVE_HISTORY
1863 _CL_HISTSIZE(hs);
1864 if (hs == 0)
1865 goto jleave;
1866 _CL_HISTFILE(v);
1867 if (v == NULL)
1868 goto jleave;
1870 dogabby = ok_blook(history_gabby_persist);
1872 if ((hp = _ncl_hist) != NULL)
1873 for (; hp->older != NULL; hp = hp->older)
1874 if ((dogabby || !hp->isgabby) && --hs == 0)
1875 break;
1877 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1878 f = fopen(v, "w"); /* TODO temporary + rename?! */
1879 if (f == NULL)
1880 goto jdone;
1881 (void)file_lock(fileno(f), FLT_WRITE, 0,0, 500);
1882 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1883 goto jclose;
1885 for (; hp != NULL; hp = hp->younger) {
1886 if (!hp->isgabby || dogabby) {
1887 if (hp->isgabby)
1888 putc('*', f);
1889 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1890 putc('\n', f);
1893 jclose:
1894 fclose(f);
1895 jdone:
1896 rele_all_sigs(); /* XXX remove jumps */
1897 jleave:
1898 # endif /* HAVE_HISTORY */
1899 NYD_LEAVE;
1902 FL void
1903 n_tty_signal(int sig)
1905 sigset_t nset, oset;
1906 NYD_X; /* Signal handler */
1908 switch (sig) {
1909 case SIGWINCH:
1910 /* We don't deal with SIGWINCH, yet get called from main.c */
1911 break;
1912 default:
1913 _ncl_term_mode(FAL0);
1914 _ncl_sigs_down();
1915 sigemptyset(&nset);
1916 sigaddset(&nset, sig);
1917 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1918 n_raise(sig);
1919 /* When we come here we'll continue editing, so reestablish */
1920 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1921 _ncl_sigs_up();
1922 _ncl_term_mode(TRU1);
1923 break;
1927 FL int
1928 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1929 SMALLOC_DEBUG_ARGS)
1931 ssize_t nn;
1932 NYD_ENTER;
1934 /* Of course we have races here, but they cannot be avoided on POSIX
1935 * (except by even *more* actions) */
1936 _ncl_sigs_up();
1937 _ncl_term_mode(TRU1);
1938 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1939 _ncl_term_mode(FAL0);
1940 _ncl_sigs_down();
1941 NYD_LEAVE;
1942 return (int)nn;
1945 FL void
1946 n_tty_addhist(char const *s, bool_t isgabby)
1948 # ifdef HAVE_HISTORY
1949 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1950 ui32_t l;
1951 struct hist *h, *o, *y;
1952 # endif
1953 NYD_ENTER;
1954 UNUSED(s);
1955 UNUSED(isgabby);
1957 # ifdef HAVE_HISTORY
1958 if (isgabby && !ok_blook(history_gabby))
1959 goto j_leave;
1960 if (_ncl_hist_size_max == 0)
1961 goto j_leave;
1962 _CL_CHECK_ADDHIST(s, goto j_leave);
1964 l = (ui32_t)strlen(s);
1966 /* Eliminating duplicates is expensive, but simply inacceptable so
1967 * during the load of a potentially large history file! */
1968 if (!_ncl_hist_load)
1969 for (h = _ncl_hist; h != NULL; h = h->older)
1970 if (h->len == l && !strcmp(h->dat, s)) {
1971 hold_all_sigs(); /* TODO */
1972 if (h->isgabby)
1973 h->isgabby = !!isgabby;
1974 o = h->older;
1975 y = h->younger;
1976 if (o != NULL)
1977 o->younger = y;
1978 else
1979 _ncl_hist_tail = y;
1980 if (y != NULL)
1981 y->older = o;
1982 else
1983 _ncl_hist = o;
1984 goto jleave;
1986 hold_all_sigs();
1988 ++_ncl_hist_size;
1989 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1990 --_ncl_hist_size;
1991 if ((h = _ncl_hist_tail) != NULL) {
1992 if ((_ncl_hist_tail = h->younger) == NULL)
1993 _ncl_hist = NULL;
1994 else
1995 _ncl_hist_tail->older = NULL;
1996 free(h);
2000 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
2001 h->isgabby = !!isgabby;
2002 h->len = l;
2003 memcpy(h->dat, s, l +1);
2004 jleave:
2005 if ((h->older = _ncl_hist) != NULL)
2006 _ncl_hist->younger = h;
2007 else
2008 _ncl_hist_tail = h;
2009 h->younger = NULL;
2010 _ncl_hist = h;
2012 rele_all_sigs();
2013 j_leave:
2014 # endif
2015 NYD_LEAVE;
2018 # ifdef HAVE_HISTORY
2019 FL int
2020 c_history(void *v)
2022 C_HISTORY_SHARED;
2024 jlist: {
2025 FILE *fp;
2026 size_t i, b;
2027 struct hist *h;
2029 if (_ncl_hist == NULL)
2030 goto jleave;
2032 if ((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
2033 n_perr(_("tmpfile"), 0);
2034 v = NULL;
2035 goto jleave;
2038 i = _ncl_hist_size;
2039 b = 0;
2040 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
2041 fprintf(fp,
2042 "%c%4" PRIuZ ". %-50.50s (%4" PRIuZ "+%2" PRIu32 " bytes)\n",
2043 (h->isgabby ? '*' : ' '), i, h->dat, b, h->len);
2045 page_or_print(fp, i);
2046 Fclose(fp);
2048 goto jleave;
2050 jclear: {
2051 struct hist *h;
2053 while ((h = _ncl_hist) != NULL) {
2054 _ncl_hist = h->older;
2055 free(h);
2057 _ncl_hist_tail = NULL;
2058 _ncl_hist_size = 0;
2060 goto jleave;
2062 jentry: {
2063 struct hist *h;
2065 if (UICMP(z, entry, <=, _ncl_hist_size)) {
2066 entry = (long)_ncl_hist_size - entry;
2067 for (h = _ncl_hist;; h = h->older)
2068 if (h == NULL)
2069 break;
2070 else if (entry-- != 0)
2071 continue;
2072 else {
2073 v = temporary_arg_v_store = h->dat;
2074 goto jleave;
2077 v = NULL;
2079 goto jleave;
2081 # endif /* HAVE_HISTORY */
2082 #endif /* HAVE_NCL */
2085 * The really-nothing-at-all implementation
2088 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
2089 FL void
2090 n_tty_init(void)
2092 NYD_ENTER;
2093 NYD_LEAVE;
2096 FL void
2097 n_tty_destroy(void)
2099 NYD_ENTER;
2100 NYD_LEAVE;
2103 FL void
2104 n_tty_signal(int sig)
2106 NYD_X; /* Signal handler */
2107 UNUSED(sig);
2110 FL int
2111 (n_tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
2112 SMALLOC_DEBUG_ARGS)
2114 int rv;
2115 NYD_ENTER;
2117 if (prompt != NULL) {
2118 if (*prompt != '\0')
2119 fputs(prompt, stdout);
2120 fflush(stdout);
2122 rv = (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
2123 NYD_LEAVE;
2124 return rv;
2127 FL void
2128 n_tty_addhist(char const *s, bool_t isgabby)
2130 NYD_ENTER;
2131 UNUSED(s);
2132 UNUSED(isgabby);
2133 NYD_LEAVE;
2135 #endif /* nothing at all */
2137 /* s-it-mode */