NEWS: update for v14.5.2
[s-mailx.git] / tty.c
blobb21093c5ea789e9d749e62566e161887728736bc
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ TTY interaction.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
39 /* The NCL version is
41 * Copyright (c) 2013 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
43 * Permission to use, copy, modify, and/or distribute this software for any
44 * purpose with or without fee is hereby granted, provided that the above
45 * copyright notice and this permission notice appear in all copies.
47 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
48 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
49 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
50 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
51 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
52 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
53 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
56 #ifndef HAVE_AMALGAMATION
57 # include "nail.h"
58 #endif
60 #ifdef HAVE_READLINE
61 # include <readline/readline.h>
62 # ifdef HAVE_HISTORY
63 # include <readline/history.h>
64 # endif
65 #elif defined HAVE_EDITLINE
66 # include <histedit.h>
67 #endif
69 /* Shared history support macros */
70 #ifdef HAVE_HISTORY
71 # define _CL_HISTFILE(S) \
72 do {\
73 S = ok_vlook(NAIL_HISTFILE);\
74 if ((S) != NULL)\
75 S = fexpand(S, FEXP_LOCAL);\
76 } while (0)
78 # define _CL_HISTSIZE(V) \
79 do {\
80 char const *__sv = ok_vlook(NAIL_HISTSIZE);\
81 long __rv;\
82 if (__sv == NULL || *__sv == '\0' || (__rv = strtol(__sv, NULL, 10)) == 0)\
83 (V) = HIST_SIZE;\
84 else if (__rv < 0)\
85 (V) = 0;\
86 else\
87 (V) = __rv;\
88 } while (0)
90 # define _CL_CHECK_ADDHIST(S,NOACT) \
91 do {\
92 switch (*(S)) {\
93 case '\0':\
94 case ' ':\
95 NOACT;\
96 default:\
97 break;\
99 } while (0)
101 # define C_HISTORY_SHARED \
102 char **argv = v;\
103 long entry;\
105 if (*argv == NULL)\
106 goto jlist;\
107 if (argv[1] != NULL)\
108 goto jerr;\
109 if (asccasecmp(*argv, "show") == 0)\
110 goto jlist;\
111 if (asccasecmp(*argv, "clear") == 0)\
112 goto jclear;\
113 if ((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
114 goto jentry;\
115 jerr:\
116 fprintf(stderr, "Synopsis: history: %s\n", tr(431,\
117 "<show> (default), <clear> or select <NO> from editor history"));\
118 v = NULL;\
119 jleave:\
120 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
121 #endif /* HAVE_HISTORY */
123 /* fexpand() flags for expand-on-tab */
124 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
127 * Because we have multiple identical implementations, change file layout a bit
128 * and place the implementations one after the other below the other externals
131 FL bool_t
132 yorn(char const *msg)
134 char *cp;
136 if (!(options & OPT_INTERACTIVE))
137 return TRU1;
138 do if ((cp = readstr_input(msg, NULL)) == NULL)
139 return FAL0;
140 while (*cp != 'y' && *cp != 'Y' && *cp != 'n' && *cp != 'N');
141 return (*cp == 'y' || *cp == 'Y');
144 FL char *
145 getuser(char const *query)
147 char *user = NULL;
149 if (query == NULL)
150 query = tr(509, "User: ");
152 if (readline_input(query, FAL0, &termios_state.ts_linebuf,
153 &termios_state.ts_linesize, NULL) >= 0)
154 user = termios_state.ts_linebuf;
155 termios_state_reset();
156 return user;
159 FL char *
160 getpassword(char const *query) /* FIXME encaps ttystate signal safe */
162 struct termios tios;
163 char *pass = NULL;
165 if (query == NULL)
166 query = tr(510, "Password: ");
167 fputs(query, stdout);
168 fflush(stdout);
171 * FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
172 * foreground pgrp, and can fail with EINTR!!
174 if (options & OPT_TTYIN) {
175 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
176 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
177 termios_state.ts_needs_reset = TRU1;
178 tios.c_iflag &= ~(ISTRIP);
179 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
180 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
183 if (readline_restart(stdin, &termios_state.ts_linebuf,
184 &termios_state.ts_linesize, 0) >= 0)
185 pass = termios_state.ts_linebuf;
186 termios_state_reset();
188 if (options & OPT_TTYIN)
189 fputc('\n', stdout);
190 return pass;
193 FL bool_t
194 getcredentials(char **user, char **pass)
196 bool_t rv = TRU1;
197 char *u = *user, *p = *pass;
199 if (u == NULL) {
200 if ((u = getuser(NULL)) == NULL)
201 rv = FAL0;
202 else if (p == NULL)
203 u = savestr(u);
204 *user = u;
207 if (p == NULL) {
208 if ((p = getpassword(NULL)) == NULL)
209 rv = FAL0;
210 *pass = p;
212 return rv;
216 * readline(3)
219 #ifdef HAVE_READLINE
220 static sighandler_type _rl_shup;
221 static char * _rl_buf; /* pre_input() hook: initial line */
222 static int _rl_buflen; /* content, and its length */
224 static int _rl_pre_input(void);
226 static int
227 _rl_pre_input(void)
229 /* Handle leftover data from \ escaped former line */
230 rl_extend_line_buffer(_rl_buflen + 10);
231 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
232 rl_point = rl_end = _rl_buflen;
233 rl_pre_input_hook = (rl_hook_func_t*)NULL;
234 rl_redisplay();
235 return 0;
238 FL void
239 tty_init(void)
241 # ifdef HAVE_HISTORY
242 long hs;
243 char *v;
244 # endif
246 rl_readline_name = UNCONST(uagent);
247 # ifdef HAVE_HISTORY
248 _CL_HISTSIZE(hs);
249 using_history();
250 stifle_history((int)hs);
251 # endif
252 rl_read_init_file(NULL);
254 /* Because rl_read_init_file() may have introduced yet a different
255 * history size limit, simply load and incorporate the history, leave
256 * it up to readline(3) to do the rest */
257 # ifdef HAVE_HISTORY
258 _CL_HISTFILE(v);
259 if (v != NULL)
260 read_history(v);
261 # endif
264 FL void
265 tty_destroy(void)
267 # ifdef HAVE_HISTORY
268 char *v;
270 _CL_HISTFILE(v);
271 if (v != NULL)
272 write_history(v);
273 # endif
277 FL void
278 tty_signal(int sig)
280 sigset_t nset, oset;
282 switch (sig) {
283 # ifdef SIGWINCH
284 case SIGWINCH:
285 break;
286 # endif
287 case SIGHUP:
288 /* readline(3) doesn't catch it :( */
289 rl_free_line_state();
290 rl_cleanup_after_signal();
291 safe_signal(SIGHUP, _rl_shup);
292 sigemptyset(&nset);
293 sigaddset(&nset, sig);
294 sigprocmask(SIG_UNBLOCK, &nset, &oset);
295 kill(0, sig);
296 /* XXX When we come here we'll continue editing, so reestablish
297 * XXX cannot happen */
298 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
299 _rl_shup = safe_signal(SIGHUP, &tty_signal);
300 rl_reset_after_signal();
301 break;
302 default:
303 break;
307 FL int
308 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
309 SMALLOC_DEBUG_ARGS)
311 int nn;
312 char *line;
314 if (n > 0) {
315 _rl_buf = *linebuf;
316 _rl_buflen = (int)n;
317 rl_pre_input_hook = &_rl_pre_input;
320 _rl_shup = safe_signal(SIGHUP, &tty_signal);
321 line = readline(prompt != NULL ? prompt : "");
322 safe_signal(SIGHUP, _rl_shup);
324 if (line == NULL) {
325 nn = -1;
326 goto jleave;
328 n = strlen(line);
330 if (n >= *linesize) {
331 *linesize = LINESIZE + n + 1;
332 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
334 memcpy(*linebuf, line, n);
335 (free)(line);
336 (*linebuf)[n] = '\0';
337 nn = (int)n;
338 jleave:
339 return nn;
342 FL void
343 tty_addhist(char const *s)
345 # ifdef HAVE_HISTORY
346 _CL_CHECK_ADDHIST(s, goto jleave);
347 hold_all_sigs(); /* XXX too heavy */
348 add_history(s); /* XXX yet we jump away! */
349 rele_all_sigs(); /* XXX remove jumps */
350 jleave:
351 # endif
352 UNUSED(s);
355 # ifdef HAVE_HISTORY
356 FL int
357 c_history(void *v)
359 C_HISTORY_SHARED;
361 jlist: {
362 FILE *fp;
363 char *cp;
364 HISTORY_STATE *hs;
365 HIST_ENTRY **hl;
366 ul_it i, b;
368 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
369 perror("tmpfile");
370 v = NULL;
371 goto jleave;
373 rm(cp);
374 Ftfree(&cp);
376 hs = history_get_history_state();
378 for (i = (ul_it)hs->length, hl = hs->entries + i, b = 0; i > 0; --i) {
379 size_t sl = strlen(cp = (*--hl)->line);
380 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n", i, cp, b, sl);
381 b += sl;
384 page_or_print(fp, (size_t)hs->length);
385 Fclose(fp);
387 goto jleave;
389 jclear:
390 clear_history();
391 goto jleave;
393 jentry: {
394 HISTORY_STATE *hs = history_get_history_state();
396 if (UICMP(z, entry, <=, hs->length))
397 v = temporary_arg_v_store = hs->entries[entry - 1]->line;
398 else
399 v = NULL;
401 goto jleave;
403 # endif /* HAVE_HISTORY */
404 #endif /* HAVE_READLINE */
407 * BSD editline(3)
410 #ifdef HAVE_EDITLINE
411 static EditLine * _el_el; /* editline(3) handle */
412 static char const * _el_prompt; /* Current prompt */
413 # ifdef HAVE_HISTORY
414 static History * _el_hcom; /* History handle for commline */
415 # endif
417 static char const * _el_getprompt(void);
419 static char const *
420 _el_getprompt(void)
422 return _el_prompt;
425 FL void
426 tty_init(void)
428 # ifdef HAVE_HISTORY
429 HistEvent he;
430 long hs;
431 char *v;
432 # endif
434 # ifdef HAVE_HISTORY
435 _CL_HISTSIZE(hs);
436 _el_hcom = history_init();
437 history(_el_hcom, &he, H_SETSIZE, (int)hs);
438 /* We unroll our own one history(_el_hcom, &he, H_SETUNIQUE, 1);*/
439 # endif
441 _el_el = el_init(uagent, stdin, stdout, stderr);
442 el_set(_el_el, EL_SIGNAL, 1);
443 el_set(_el_el, EL_TERMINAL, NULL);
444 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
445 # ifdef HAVE_HISTORY
446 el_set(_el_el, EL_HIST, &history, _el_hcom);
447 # endif
448 el_set(_el_el, EL_EDITOR, "emacs");
449 # ifdef EL_PROMPT_ESC
450 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
451 # else
452 el_set(_el_el, EL_PROMPT, &_el_getprompt);
453 # endif
454 # if 0
455 el_set(_el_el, EL_ADDFN, "tab_complete",
456 "editline(3) internal completion function", &_el_file_cpl);
457 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
458 # endif
459 # ifdef HAVE_HISTORY
460 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
461 # endif
462 el_source(_el_el, NULL); /* Source ~/.editrc */
464 /* Because el_source() may have introduced yet a different history size
465 * limit, simply load and incorporate the history, leave it up to
466 * editline(3) to do the rest */
467 # ifdef HAVE_HISTORY
468 _CL_HISTFILE(v);
469 if (v != NULL)
470 history(_el_hcom, &he, H_LOAD, v);
471 # endif
474 FL void
475 tty_destroy(void)
477 # ifdef HAVE_HISTORY
478 HistEvent he;
479 char *v;
480 # endif
482 el_end(_el_el);
484 # ifdef HAVE_HISTORY
485 _CL_HISTFILE(v);
486 if (v != NULL)
487 history(_el_hcom, &he, H_SAVE, v);
488 history_end(_el_hcom);
489 # endif
492 FL void
493 tty_signal(int sig)
495 switch (sig) {
496 # ifdef SIGWINCH
497 case SIGWINCH:
498 el_resize(_el_el);
499 break;
500 # endif
501 default:
502 break;
506 FL int
507 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
508 SMALLOC_DEBUG_ARGS)
510 int nn;
511 char const *line;
513 _el_prompt = (prompt != NULL) ? prompt : "";
514 if (n > 0)
515 el_push(_el_el, *linebuf);
516 line = el_gets(_el_el, &nn);
518 if (line == NULL) {
519 nn = -1;
520 goto jleave;
522 assert(nn >= 0);
523 n = (size_t)nn;
524 if (n > 0 && line[n - 1] == '\n')
525 nn = (int)--n;
527 if (n >= *linesize) {
528 *linesize = LINESIZE + n + 1;
529 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
531 memcpy(*linebuf, line, n);
532 (*linebuf)[n] = '\0';
533 jleave:
534 return nn;
537 FL void
538 tty_addhist(char const *s)
540 # ifdef HAVE_HISTORY
541 /* Enlarge meaning of unique .. to something that rocks;
542 * xxx unfortunately this is expensive to do with editline(3)
543 * xxx maybe it would be better to hook the ptfs instead? */
544 HistEvent he;
545 int i;
547 _CL_CHECK_ADDHIST(s, goto jleave);
549 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
550 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
551 i = history(_el_hcom, &he, H_NEXT))
552 if (strcmp(he.str, s) == 0) {
553 history(_el_hcom, &he, H_DEL, he.num);
554 break;
556 history(_el_hcom, &he, H_ENTER, s);
557 rele_all_sigs(); /* XXX remove jumps */
558 jleave:
559 # endif
560 UNUSED(s);
563 # ifdef HAVE_HISTORY
564 FL int
565 c_history(void *v)
567 C_HISTORY_SHARED;
569 jlist: {
570 HistEvent he;
571 FILE *fp;
572 char *cp;
573 size_t i, b;
574 int x;
576 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
577 perror("tmpfile");
578 v = NULL;
579 goto jleave;
581 rm(cp);
582 Ftfree(&cp);
584 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
585 b = 0;
586 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
587 x = history(_el_hcom, &he, H_NEXT)) {
588 size_t sl = strlen(he.str);
589 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
590 (ul_it)i, he.str, (ul_it)b, (ul_it)sl);
591 --i;
592 b += sl;
595 page_or_print(fp, i);
596 Fclose(fp);
598 goto jleave;
600 jclear: {
601 HistEvent he;
602 history(_el_hcom, &he, H_CLEAR);
604 goto jleave;
606 jentry: {
607 HistEvent he;
608 size_t i;
609 int x;
611 i = (size_t)((history(_el_hcom, &he, H_GETSIZE) >= 0) ? he.num : 0);
612 if (UICMP(z, entry, <=, i)) {
613 entry = (long)i - entry;
614 for (x = history(_el_hcom, &he, H_FIRST); x >= 0;
615 x = history(_el_hcom, &he, H_NEXT))
616 if (entry-- == 0) {
617 v = temporary_arg_v_store = UNCONST(he.str);
618 goto jleave;
621 v = NULL;
623 goto jleave;
625 # endif /* HAVE_HISTORY */
626 #endif /* HAVE_EDITLINE */
629 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
631 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
632 * We do not handle character widths because the terminal must deal with that
633 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
634 * characters by definition on the other. We're addicted.
636 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
637 * we're forced to use the very same buffer--the one that is passed through to
638 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
639 * convert that on-the-fly back to the plain char* result once we're done.
640 * To simplify our live, use savestr() buffers for all other needed memory
644 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
645 * TODO NCL: during handler de-/installation handling.
648 #ifdef HAVE_NCL
649 # ifndef MAX_INPUT
650 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
651 # endif
653 /* Since we simply fputs(3) the prompt, assume each character requires two
654 * visual cells -- and we need to restrict the maximum prompt size because
655 * of MAX_INPUT and our desire to have room for some error message left */
656 # define _PROMPT_VLEN(P) (strlen(P) * 2)
657 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
659 union xsighdl {
660 sighandler_type shdl; /* Try avoid races by setting */
661 sl_it sint; /* .sint=-1 when inactive */
663 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
665 struct xtios {
666 struct termios told;
667 struct termios tnew;
670 struct cell {
671 wchar_t wc;
672 ui_it count;
673 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
676 struct line {
677 size_t cursor; /* Current cursor position */
678 size_t topins; /* Outermost cursor col set */
679 union {
680 char * cbuf; /* *x_buf */
681 struct cell * cells;
682 } line;
683 struct str defc; /* Current default content */
684 struct str savec; /* Saved default content */
685 # ifdef HAVE_HISTORY
686 struct hist * hist; /* History cursor */
687 # endif
688 char const * prompt;
689 char const * nd; /* Cursor right */
690 char ** x_buf; /* Caller pointers */
691 size_t * x_bufsize;
694 # ifdef HAVE_HISTORY
695 struct hist {
696 struct hist * older;
697 struct hist * younger;
698 size_t len;
699 char dat[VFIELD_SIZE(sizeof(size_t))];
701 # endif
703 static union xsighdl _ncl_oint;
704 static union xsighdl _ncl_oquit;
705 static union xsighdl _ncl_oterm;
706 static union xsighdl _ncl_ohup;
707 static union xsighdl _ncl_otstp;
708 static union xsighdl _ncl_ottin;
709 static union xsighdl _ncl_ottou;
710 static struct xtios _ncl_tios;
711 # ifdef HAVE_HISTORY
712 static struct hist * _ncl_hist;
713 static struct hist * _ncl_hist_tail;
714 static size_t _ncl_hist_size;
715 static size_t _ncl_hist_size_max;
716 static bool_t _ncl_hist_load;
717 # endif
719 static void _ncl_sigs_up(void);
720 static void _ncl_sigs_down(void);
722 static void _ncl_term_mode(bool_t raw);
724 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
725 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
726 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
727 static ssize_t _ncl_cell2dat(struct line *l);
728 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
729 static void _ncl_cell2save(struct line *l);
730 # endif
732 static void _ncl_khome(struct line *l, bool_t dobell);
733 static void _ncl_kend(struct line *l);
734 static void _ncl_kbs(struct line *l);
735 static void _ncl_kkill(struct line *l, bool_t dobell);
736 static ssize_t _ncl_keof(struct line *l);
737 static void _ncl_kleft(struct line *l);
738 static void _ncl_kright(struct line *l);
739 static void _ncl_krefresh(struct line *l);
740 static void _ncl_kbwddelw(struct line *l);
741 static void _ncl_kgow(struct line *l, ssize_t dir);
742 static void _ncl_kother(struct line *l, wchar_t wc);
743 # ifdef HAVE_HISTORY
744 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
745 static size_t _ncl_khist(struct line *l, bool_t backwd);
746 static size_t _ncl_krhist(struct line *l);
747 # endif
748 # ifdef HAVE_TABEXPAND
749 static size_t _ncl_kht(struct line *l);
750 # endif
751 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
752 size_t len SMALLOC_DEBUG_ARGS);
754 static void
755 _ncl_sigs_up(void)
757 if (_ncl_oint.sint == -1)
758 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
759 if (_ncl_oquit.sint == -1)
760 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
761 if (_ncl_oterm.sint == -1)
762 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
763 if (_ncl_ohup.sint == -1)
764 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
765 if (_ncl_otstp.sint == -1)
766 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
767 if (_ncl_ottin.sint == -1)
768 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
769 if (_ncl_ottou.sint == -1)
770 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
773 static void
774 _ncl_sigs_down(void)
776 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
777 sighandler_type st;
779 if (_ncl_ottou.sint != -1) {
780 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
781 safe_signal(SIGTTOU, st);
783 if (_ncl_ottin.sint != -1) {
784 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
785 safe_signal(SIGTTIN, st);
787 if (_ncl_otstp.sint != -1) {
788 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
789 safe_signal(SIGTSTP, st);
791 if (_ncl_ohup.sint != -1) {
792 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
793 safe_signal(SIGHUP, st);
795 if (_ncl_oterm.sint != -1) {
796 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
797 safe_signal(SIGTERM, st);
799 if (_ncl_oquit.sint != -1) {
800 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
801 safe_signal(SIGQUIT, st);
803 if (_ncl_oint.sint != -1) {
804 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
805 safe_signal(SIGINT, st);
809 static void
810 _ncl_term_mode(bool_t raw)
812 struct termios *tiosp = &_ncl_tios.told;
814 if (!raw)
815 goto jleave;
817 /* Always requery the attributes, in case we've been moved from background
818 * to foreground or however else in between sessions */
819 tcgetattr(STDIN_FILENO, tiosp);
820 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
821 tiosp = &_ncl_tios.tnew;
822 tiosp->c_cc[VMIN] = 1;
823 tiosp->c_cc[VTIME] = 0;
824 tiosp->c_iflag &= ~(ISTRIP);
825 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
826 jleave:
827 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
830 static void
831 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
833 size_t i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
835 if (i > *l->x_bufsize) {
836 i <<= 1;
837 *l->x_bufsize = i;
838 l->line.cbuf =
839 *l->x_buf = (srealloc)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
843 static void
844 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
846 size_t j;
848 if (i > 0)
849 memmove(cap, cap + 1, i * sizeof(*cap));
851 /* And.. the (rest of the) visual update */
852 for (j = 0; j < i; ++j)
853 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
854 fputs(" \b", stdout);
855 for (j = 0; j < i; ++j)
856 putchar('\b');
859 static ssize_t
860 _ncl_wboundary(struct line *l, ssize_t dir)
862 size_t c = l->cursor, t = l->topins, i;
863 struct cell *cap;
864 bool_t anynon;
866 i = (size_t)-1;
867 if (dir < 0) {
868 if (c == 0)
869 goto jleave;
870 } else if (c == t)
871 goto jleave;
872 else
873 --t, --c; /* Unsigned wrapping may occur (twice), then */
875 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
876 wchar_t wc = cap[c + dir].wc;
877 if (iswblank(wc) || iswpunct(wc)) {
878 if (anynon)
879 break;
880 } else
881 anynon = TRU1;
882 ++i;
883 c += dir;
884 if (dir < 0) {
885 if (c == 0)
886 break;
887 } else if (c == t)
888 break;
890 jleave:
891 return (ssize_t)i;
894 static ssize_t
895 _ncl_cell2dat(struct line *l)
897 size_t len = 0, i;
899 if (l->topins > 0)
900 for (i = 0; i < l->topins; ++i) {
901 struct cell *cap = l->line.cells + i;
902 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
903 len += cap->count;
905 l->line.cbuf[len] = '\0';
906 return (ssize_t)len;
909 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
910 static void
911 _ncl_cell2save(struct line *l)
913 size_t len, i;
914 struct cell *cap;
916 l->savec.s = NULL, l->savec.l = 0;
917 if (l->topins == 0)
918 goto jleave;
920 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
921 len += cap->count;
923 l->savec.l = len;
924 l->savec.s = salloc(len + 1);
926 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
927 memcpy(l->savec.s + len, cap->cbuf, cap->count);
928 len += cap->count;
930 l->savec.s[len] = '\0';
931 jleave:
934 # endif
936 static void
937 _ncl_khome(struct line *l, bool_t dobell)
939 size_t c = l->cursor;
941 if (c > 0) {
942 l->cursor = 0;
943 while (c-- != 0)
944 putchar('\b');
945 } else if (dobell)
946 putchar('\a');
949 static void
950 _ncl_kend(struct line *l)
952 ssize_t i = (ssize_t)(l->topins - l->cursor);
954 if (i > 0) {
955 l->cursor = l->topins;
956 while (i-- != 0)
957 fputs(l->nd, stdout);
958 } else
959 putchar('\a');
962 static void
963 _ncl_kbs(struct line *l)
965 ssize_t c = l->cursor, t = l->topins;
967 if (c > 0) {
968 putchar('\b');
969 l->cursor = --c;
970 l->topins = --t;
971 t -= c;
972 _ncl_bs_eof_dvup(l->line.cells + c, t);
973 } else
974 putchar('\a');
977 static void
978 _ncl_kkill(struct line *l, bool_t dobell)
980 size_t j, c = l->cursor, i = (size_t)(l->topins - c);
982 if (i > 0) {
983 l->topins = c;
984 for (j = i; j != 0; --j)
985 putchar(' ');
986 for (j = i; j != 0; --j)
987 putchar('\b');
988 } else if (dobell)
989 putchar('\a');
992 static ssize_t
993 _ncl_keof(struct line *l)
995 size_t c = l->cursor, t = l->topins;
996 ssize_t i = (ssize_t)(t - c);
998 if (i > 0) {
999 l->topins = --t;
1000 _ncl_bs_eof_dvup(l->line.cells + c, --i);
1001 } else if (t == 0 && !ok_blook(ignoreeof)) {
1002 fputs("^D", stdout);
1003 fflush(stdout);
1004 i = -1;
1005 } else {
1006 putchar('\a');
1007 i = 0;
1009 return i;
1012 static void
1013 _ncl_kleft(struct line *l)
1015 if (l->cursor > 0) {
1016 --l->cursor;
1017 putchar('\b');
1018 } else
1019 putchar('\a');
1022 static void
1023 _ncl_kright(struct line *l)
1025 if (l->cursor < l->topins) {
1026 ++l->cursor;
1027 fputs(l->nd, stdout);
1028 } else
1029 putchar('\a');
1032 static void
1033 _ncl_krefresh(struct line *l)
1035 struct cell *cap;
1036 size_t i;
1038 putchar('\r');
1039 if (l->prompt != NULL && *l->prompt != '\0')
1040 fputs(l->prompt, stdout);
1041 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
1042 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1043 for (i = l->topins - l->cursor; i > 0; --i)
1044 putchar('\b');
1047 static void
1048 _ncl_kbwddelw(struct line *l)
1050 ssize_t i;
1051 size_t c = l->cursor, t, j;
1052 struct cell *cap;
1054 i = _ncl_wboundary(l, -1);
1055 if (i <= 0) {
1056 if (i < 0)
1057 putchar('\a');
1058 goto jleave;
1061 c = l->cursor - i;
1062 t = l->topins;
1063 l->topins = t - i;
1064 l->cursor = c;
1065 cap = l->line.cells + c;
1067 if (t != l->cursor) {
1068 j = t - c + i;
1069 memmove(cap, cap + i, j * sizeof(*cap));
1072 for (j = i; j > 0; --j)
1073 putchar('\b');
1074 for (j = l->topins - c; j > 0; ++cap, --j)
1075 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
1076 for (j = i; j > 0; --j)
1077 putchar(' ');
1078 for (j = t - c; j > 0; --j)
1079 putchar('\b');
1080 jleave:
1084 static void
1085 _ncl_kgow(struct line *l, ssize_t dir)
1087 ssize_t i = _ncl_wboundary(l, dir);
1088 if (i <= 0) {
1089 if (i < 0)
1090 putchar('\a');
1091 goto jleave;
1094 if (dir < 0) {
1095 l->cursor -= i;
1096 while (i-- > 0)
1097 putchar('\b');
1098 } else {
1099 l->cursor += i;
1100 while (i-- > 0)
1101 fputs(l->nd, stdout);
1103 jleave:
1107 static void
1108 _ncl_kother(struct line *l, wchar_t wc)
1110 /* Append if at EOL, insert otherwise;
1111 * since we may move around character-wise, always use a fresh ps */
1112 mbstate_t ps;
1113 struct cell cell, *cap;
1114 size_t i, c;
1116 /* First init a cell and see wether we'll really handle this wc */
1117 cell.wc = wc;
1118 memset(&ps, 0, sizeof ps);
1119 i = wcrtomb(cell.cbuf, wc, &ps);
1120 if (i > MB_LEN_MAX)
1121 goto jleave;
1122 cell.count = (ui_it)i;
1123 if (enc_has_state) {
1124 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
1125 if (i == 1)
1127 else if (--i < MB_LEN_MAX)
1128 cell.count += (ui_it)i;
1129 else
1130 goto jleave;
1133 /* Yes, we will! Place it in the array */
1134 c = l->cursor++;
1135 i = l->topins++ - c;
1136 cap = l->line.cells + c;
1137 if (i > 0)
1138 memmove(cap + 1, cap, i * sizeof(cell));
1139 memcpy(cap, &cell, sizeof cell);
1141 /* And update visual */
1142 c = i;
1144 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1145 while ((++cap, i-- != 0));
1146 while (c-- != 0)
1147 putchar('\b');
1148 jleave:
1152 # ifdef HAVE_HISTORY
1153 static size_t
1154 __ncl_khist_shared(struct line *l, struct hist *hp)
1156 size_t rv;
1158 if ((l->hist = hp) != NULL) {
1159 l->defc.s = savestrbuf(hp->dat, hp->len);
1160 rv =
1161 l->defc.l = hp->len;
1162 if (l->topins > 0) {
1163 _ncl_khome(l, FAL0);
1164 _ncl_kkill(l, FAL0);
1166 } else {
1167 putchar('\a');
1168 rv = 0;
1170 return rv;
1173 static size_t
1174 _ncl_khist(struct line *l, bool_t backwd)
1176 struct hist *hp;
1178 /* If we're not in history mode yet, save line content;
1179 * also, disallow forward search, then, and, of course, bail unless we
1180 * do have any history at all */
1181 if ((hp = l->hist) == NULL) {
1182 if (!backwd)
1183 goto jleave;
1184 if ((hp = _ncl_hist) == NULL)
1185 goto jleave;
1186 _ncl_cell2save(l);
1187 goto jleave;
1190 hp = backwd ? hp->older : hp->younger;
1191 jleave:
1192 return __ncl_khist_shared(l, hp);
1195 static size_t
1196 _ncl_krhist(struct line *l)
1198 struct str orig_savec;
1199 struct hist *hp = NULL;
1201 /* We cannot complete an empty line */
1202 if (l->topins == 0) {
1203 /* XXX The upcoming hard reset would restore a set savec buffer,
1204 * XXX so forcefully reset that. A cleaner solution would be to
1205 * XXX reset it whenever a restore is no longer desired */
1206 l->savec.s = NULL, l->savec.l = 0;
1207 goto jleave;
1209 if ((hp = l->hist) == NULL) {
1210 if ((hp = _ncl_hist) == NULL)
1211 goto jleave;
1212 orig_savec.s = NULL;
1213 orig_savec.l = 0; /* silence CC */
1214 } else if ((hp = hp->older) == NULL)
1215 goto jleave;
1216 else
1217 orig_savec = l->savec;
1219 if (orig_savec.s == NULL)
1220 _ncl_cell2save(l);
1221 for (; hp != NULL; hp = hp->older)
1222 if (is_prefix(l->savec.s, hp->dat))
1223 break;
1224 if (orig_savec.s != NULL)
1225 l->savec = orig_savec;
1226 jleave:
1227 return __ncl_khist_shared(l, hp);
1229 # endif
1231 # ifdef HAVE_TABEXPAND
1232 static size_t
1233 _ncl_kht(struct line *l)
1235 struct str orig, bot, topp, sub, exp;
1236 struct cell *cword, *ctop, *cx;
1237 bool_t set_savec = FAL0;
1238 size_t rv = 0;
1240 /* We cannot expand an empty line */
1241 if (l->topins == 0)
1242 goto jleave;
1244 /* Get plain line data; if this is the first expansion/xy, update the
1245 * very original content so that ^G gets the origin back */
1246 orig = l->savec;
1247 _ncl_cell2save(l);
1248 exp = l->savec;
1249 if (orig.s != NULL)
1250 l->savec = orig;
1251 else
1252 set_savec = TRU1;
1253 orig = exp;
1255 cword = l->line.cells;
1256 ctop = cword + l->cursor;
1258 /* topp: separate data right of cursor */
1259 if ((cx = cword + l->topins) != ctop) {
1260 for (rv = 0; cx > ctop; --cx)
1261 rv += cx->count;
1262 topp.l = rv;
1263 topp.s = orig.s + orig.l - rv;
1264 } else
1265 topp.s = NULL, topp.l = 0;
1267 /* bot, sub: we cannot expand the entire data left of cursor, but only
1268 * the last "word", so separate them */
1269 while (cx > cword && !iswspace(cx[-1].wc))
1270 --cx;
1271 for (rv = 0; cword < cx; ++cword)
1272 rv += cword->count;
1273 sub =
1274 bot = orig;
1275 bot.l = rv;
1276 sub.s += rv;
1277 sub.l -= rv;
1278 sub.l -= topp.l;
1280 if (sub.l > 0) {
1281 sub.s = savestrbuf(sub.s, sub.l);
1282 /* TODO there is a TODO note upon fexpand() with multi-return;
1283 * TODO if that will change, the if() below can be simplified */
1284 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1285 hold_all_sigs();
1286 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1287 rele_all_sigs();
1289 if (exp.s != NULL && (exp.l = strlen(exp.s)) > 0 &&
1290 (exp.l != sub.l || strcmp(exp.s, sub.s))) {
1291 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1292 * Take care to take *prompt* into account, since we don't know
1293 * anything about it's visual length (fputs(3) is used), simply
1294 * assume each character requires two columns */
1295 /* TODO the problem is that we loose control otherwise; in the best
1296 * TODO case the user can control via ^A and ^K etc., but be safe;
1297 * TODO we cannot simply adjust fexpand() because we don't know how
1298 * TODO that is implemented... The real solution would be to check
1299 * TODO wether we fit on a line, and start a pager if not.
1300 * TODO However, that should be part of a real tab-COMPLETION, then,
1301 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1302 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1303 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1304 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1305 char const e1[] = "[maximum line size exceeded]";
1306 exp.s = UNCONST(e1);
1307 exp.l = sizeof(e1) - 1;
1308 topp.l = 0;
1309 if (rv + bot.l + exp.l >= MAX_INPUT)
1310 bot.l = 0;
1311 if (rv + exp.l >= MAX_INPUT) {
1312 char const e2[] = "[ERR]";
1313 exp.s = UNCONST(e2);
1314 exp.l = sizeof(e2) - 1;
1317 orig.l = bot.l + exp.l + topp.l;
1318 orig.s = salloc(orig.l + 1 + 5);
1319 if ((rv = bot.l) > 0)
1320 memcpy(orig.s, bot.s, rv);
1321 memcpy(orig.s + rv, exp.s, exp.l);
1322 rv += exp.l;
1323 if (topp.l > 0) {
1324 memcpy(orig.s + rv, topp.s, topp.l);
1325 rv += topp.l;
1327 orig.s[rv] = '\0';
1329 l->defc = orig;
1330 _ncl_khome(l, FAL0);
1331 _ncl_kkill(l, FAL0);
1332 goto jleave;
1336 /* If we've provided a default content, but failed to expand, there is
1337 * nothing we can "revert to": drop that default again */
1338 if (set_savec)
1339 l->savec.s = NULL, l->savec.l = 0;
1340 rv = 0;
1341 jleave:
1342 return rv;
1344 # endif /* HAVE_TABEXPAND */
1346 static ssize_t
1347 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1348 SMALLOC_DEBUG_ARGS)
1350 /* We want to save code, yet we may have to incorporate a lines'
1351 * default content and / or default input to switch back to after some
1352 * history movement; let "len > 0" mean "have to display some data
1353 * buffer", and only otherwise read(2) it */
1354 mbstate_t ps[2];
1355 struct line l;
1356 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
1357 wchar_t wc;
1358 ssize_t rv;
1359 ui32_t maybe_cursor;
1361 memset(&l, 0, sizeof l);
1362 l.line.cbuf = *buf;
1363 if (len != 0) {
1364 l.defc.s = savestrbuf(*buf, len);
1365 l.defc.l = len;
1367 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1368 l.prompt = prompt = "?ERR?";
1369 /* TODO *l.nd=='\0' only because we have no value-cache -> see acmava.c */
1370 if ((l.nd = ok_vlook(line_editor_cursor_right)) == NULL || *l.nd == '\0')
1371 l.nd = "\033[C"; /* XXX no "magic" constant */
1372 l.x_buf = buf;
1373 l.x_bufsize = bufsize;
1375 if (prompt != NULL && *prompt != '\0') {
1376 fputs(prompt, stdout);
1377 fflush(stdout);
1379 jrestart:
1380 memset(ps, 0, sizeof ps);
1381 maybe_cursor = 0;
1382 /* TODO: NCL: we should output the reset sequence when we jrestart:
1383 * TODO: NCL: if we are using a stateful encoding? !
1384 * TODO: NCL: in short: this is not yet well understood */
1385 for (;;) {
1386 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1388 /* Normal read(2)? Else buffer-takeover: speed this one up */
1389 if (len == 0)
1390 cbufp =
1391 cbuf = cbuf_base;
1392 else {
1393 assert(l.defc.l > 0 && l.defc.s != NULL);
1394 cbufp =
1395 cbuf = l.defc.s + (l.defc.l - len);
1396 cbufp += len;
1399 /* Read in the next complete multibyte character */
1400 for (;;) {
1401 if (len == 0) {
1402 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1403 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1404 continue;
1405 goto jleave;
1407 ++cbufp;
1410 /* Ach! the ISO C multibyte handling!
1411 * Encodings with locking shift states cannot really be helped, since
1412 * it is impossible to only query the shift state, as opposed to the
1413 * entire shift state + character pair (via ISO C functions) */
1414 rv = (ssize_t)mbrtowc(&wc, cbuf, (size_t)(cbufp - cbuf), ps + 0);
1415 if (rv <= 0) {
1416 /* Any error during take-over can only result in a hard reset;
1417 * Otherwise, if it's a hard error, or if too many redundant shift
1418 * sequences overflow our buffer, also perform a hard reset */
1419 if (len != 0 || rv == -1 ||
1420 sizeof cbuf_base == (size_t)(cbufp - cbuf)) {
1421 l.savec.s = l.defc.s = NULL,
1422 l.savec.l = l.defc.l = len = 0;
1423 putchar('\a');
1424 wc = 'G';
1425 goto jreset;
1427 /* Otherwise, due to the way we deal with the buffer, we need to
1428 * restore the mbstate_t from before this conversion */
1429 ps[0] = ps[1];
1430 continue;
1433 if (len != 0 && (len -= (size_t)rv) == 0)
1434 l.defc.s = NULL, l.defc.l = 0;
1435 ps[1] = ps[0];
1436 break;
1439 /* Don't interpret control bytes during buffer take-over */
1440 if (cbuf != cbuf_base)
1441 goto jprint;
1442 switch (wc) {
1443 case 'A' ^ 0x40: /* cursor home */
1444 _ncl_khome(&l, TRU1);
1445 break;
1446 j_b:
1447 case 'B' ^ 0x40: /* backward character */
1448 _ncl_kleft(&l);
1449 break;
1450 /* 'C': interrupt (CTRL-C) */
1451 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1452 if ((rv = _ncl_keof(&l)) < 0)
1453 goto jleave;
1454 break;
1455 case 'E' ^ 0x40: /* end of line */
1456 _ncl_kend(&l);
1457 break;
1458 j_f:
1459 case 'F' ^ 0x40: /* forward character */
1460 _ncl_kright(&l);
1461 break;
1462 /* 'G' below */
1463 case 'H' ^ 0x40: /* backspace */
1464 case '\177':
1465 _ncl_kbs(&l);
1466 break;
1467 case 'I' ^ 0x40: /* horizontal tab */
1468 # ifdef HAVE_TABEXPAND
1469 if ((len = _ncl_kht(&l)) > 0)
1470 goto jrestart;
1471 # endif
1472 goto jbell;
1473 case 'J' ^ 0x40: /* NL (\n) */
1474 goto jdone;
1475 case 'G' ^ 0x40: /* full reset */
1476 jreset:
1477 /* FALLTHRU */
1478 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1479 _ncl_khome(&l, FAL0);
1480 /* FALLTHRU */
1481 case 'K' ^ 0x40: /* kill from cursor to end of line */
1482 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1483 /* (Handle full reset?) */
1484 if (wc == ('G' ^ 0x40)) {
1485 # ifdef HAVE_HISTORY
1486 l.hist = NULL;
1487 # endif
1488 if ((len = l.savec.l) != 0) {
1489 l.defc = l.savec;
1490 l.savec.s = NULL, l.savec.l = 0;
1491 } else
1492 len = l.defc.l;
1494 fflush(stdout);
1495 goto jrestart;
1496 case 'L' ^ 0x40: /* repaint line */
1497 _ncl_krefresh(&l);
1498 break;
1499 /* 'M': CR (\r) */
1500 j_n:
1501 case 'N' ^ 0x40: /* history next */
1502 # ifdef HAVE_HISTORY
1503 if (l.hist == NULL)
1504 goto jbell;
1505 if ((len = _ncl_khist(&l, FAL0)) > 0)
1506 goto jrestart;
1507 wc = 'G' ^ 0x40;
1508 goto jreset;
1509 # else
1510 goto jbell;
1511 # endif
1512 /* 'O' */
1513 j_p:
1514 case 'P' ^ 0x40: /* history previous */
1515 # ifdef HAVE_HISTORY
1516 if ((len = _ncl_khist(&l, TRU1)) > 0)
1517 goto jrestart;
1518 wc = 'G' ^ 0x40;
1519 goto jreset;
1520 # else
1521 goto jbell;
1522 # endif
1523 /* 'Q': no code */
1524 case 'R' ^ 0x40: /* reverse history search */
1525 # ifdef HAVE_HISTORY
1526 if ((len = _ncl_krhist(&l)) > 0)
1527 goto jrestart;
1528 wc = 'G' ^ 0x40;
1529 goto jreset;
1530 # else
1531 goto jbell;
1532 # endif
1533 /* 'S': no code */
1534 /* 'U' above */
1535 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1536 case 'W' ^ 0x40: /* backward delete "word" */
1537 _ncl_kbwddelw(&l);
1538 break;
1539 case 'X' ^ 0x40: /* move cursor forward "word" */
1540 _ncl_kgow(&l, +1);
1541 break;
1542 case 'Y' ^ 0x40: /* move cursor backward "word" */
1543 _ncl_kgow(&l, -1);
1544 break;
1545 /* 'Z': suspend (CTRL-Z) */
1546 case 0x1B:
1547 if (maybe_cursor++ != 0)
1548 goto jreset;
1549 continue;
1550 default:
1551 /* XXX Handle usual ^[[[ABCD] cursor keys -- UGLY, "MAGIC", INFLEX */
1552 if (maybe_cursor > 0) {
1553 if (++maybe_cursor == 2) {
1554 if (wc == L'[')
1555 continue;
1556 maybe_cursor = 0;
1557 } else {
1558 maybe_cursor = 0;
1559 switch (wc) {
1560 case L'A': goto j_p;
1561 case L'B': goto j_n;
1562 case L'C': goto j_f;
1563 case L'D': goto j_b;
1565 _ncl_kother(&l, L'[');
1568 jprint:
1569 if (iswprint(wc)) {
1570 _ncl_kother(&l, wc);
1571 /* Don't clear the history during takeover..
1572 * ..and also avoid fflush()ing unless we've
1573 * worked the entire buffer */
1574 if (len > 0)
1575 continue;
1576 # ifdef HAVE_HISTORY
1577 if (cbuf == cbuf_base)
1578 l.hist = NULL;
1579 # endif
1580 } else {
1581 jbell:
1582 putchar('\a');
1584 break;
1586 fflush(stdout);
1589 /* We have a completed input line, convert the struct cell data to its
1590 * plain character equivalent */
1591 jdone:
1592 putchar('\n');
1593 fflush(stdout);
1594 len = _ncl_cell2dat(&l);
1595 rv = (ssize_t)len;
1596 jleave:
1597 return rv;
1600 FL void
1601 tty_init(void)
1603 # ifdef HAVE_HISTORY
1604 long hs;
1605 char *v, *lbuf;
1606 FILE *f;
1607 size_t lsize, cnt, llen;
1608 # endif
1610 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1611 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1612 _ncl_ottou.sint = -1;
1614 # ifdef HAVE_HISTORY
1615 _CL_HISTSIZE(hs);
1616 _ncl_hist_size = 0;
1617 _ncl_hist_size_max = hs;
1618 if (hs == 0)
1619 goto jleave;
1621 _CL_HISTFILE(v);
1622 if (v == NULL)
1623 goto jleave;
1625 hold_all_sigs(); /* XXX too heavy, yet we may jump even here!? */
1626 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1627 if (f == NULL)
1628 goto jdone;
1630 lbuf = NULL;
1631 lsize = 0;
1632 cnt = fsize(f);
1633 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1634 if (llen > 0 && lbuf[llen - 1] == '\n')
1635 lbuf[--llen] = '\0';
1636 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1637 continue;
1638 _ncl_hist_load = TRU1;
1639 tty_addhist(lbuf);
1640 _ncl_hist_load = FAL0;
1642 if (lbuf != NULL)
1643 free(lbuf);
1645 fclose(f);
1646 jdone:
1647 rele_all_sigs(); /* XXX remove jumps */
1648 jleave:
1649 # endif /* HAVE_HISTORY */
1653 FL void
1654 tty_destroy(void)
1656 # ifdef HAVE_HISTORY
1657 long hs;
1658 char *v;
1659 struct hist *hp;
1660 FILE *f;
1662 _CL_HISTSIZE(hs);
1663 if (hs == 0)
1664 goto jleave;
1666 _CL_HISTFILE(v);
1667 if (v == NULL)
1668 goto jleave;
1670 if ((hp = _ncl_hist) != NULL)
1671 while (hp->older != NULL && hs-- != 0)
1672 hp = hp->older;
1674 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
1675 f = fopen(v, "w"); /* TODO temporary + rename?! */
1676 if (f == NULL)
1677 goto jdone;
1678 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1679 goto jclose;
1681 for (; hp != NULL; hp = hp->younger) {
1682 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1683 putc('\n', f);
1685 jclose:
1686 fclose(f);
1687 jdone:
1688 rele_all_sigs(); /* XXX remove jumps */
1689 jleave:
1690 # endif /* HAVE_HISTORY */
1694 FL void
1695 tty_signal(int sig)
1697 sigset_t nset, oset;
1699 switch (sig) {
1700 case SIGWINCH:
1701 /* We don't deal with SIGWINCH, yet get called from main.c */
1702 break;
1703 default:
1704 _ncl_term_mode(FAL0);
1705 _ncl_sigs_down();
1706 sigemptyset(&nset);
1707 sigaddset(&nset, sig);
1708 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1709 kill(0, sig);
1710 /* When we come here we'll continue editing, so reestablish */
1711 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1712 _ncl_sigs_up();
1713 _ncl_term_mode(TRU1);
1714 break;
1718 FL int
1719 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1720 SMALLOC_DEBUG_ARGS)
1722 ssize_t nn;
1724 /* Of course we have races here, but they cannot be avoided on POSIX
1725 * (except by even *more* actions) */
1726 _ncl_sigs_up();
1727 _ncl_term_mode(TRU1);
1728 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1729 _ncl_term_mode(FAL0);
1730 _ncl_sigs_down();
1732 return (int)nn;
1735 FL void
1736 tty_addhist(char const *s)
1738 # ifdef HAVE_HISTORY
1739 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1740 size_t l = strlen(s);
1741 struct hist *h, *o, *y;
1743 if (_ncl_hist_size_max == 0)
1744 goto j_leave;
1745 _CL_CHECK_ADDHIST(s, goto j_leave);
1747 /* Eliminating duplicates is expensive, but simply inacceptable so
1748 * during the load of a potentially large history file! */
1749 if (!_ncl_hist_load)
1750 for (h = _ncl_hist; h != NULL; h = h->older)
1751 if (h->len == l && !strcmp(h->dat, s)) {
1752 hold_all_sigs(); /* TODO */
1753 o = h->older;
1754 y = h->younger;
1755 if (o != NULL)
1756 o->younger = y;
1757 else
1758 _ncl_hist_tail = y;
1759 if (y != NULL)
1760 y->older = o;
1761 else
1762 _ncl_hist = o;
1763 goto jleave;
1765 hold_all_sigs();
1767 ++_ncl_hist_size;
1768 if (!_ncl_hist_load && _ncl_hist_size > _ncl_hist_size_max) {
1769 --_ncl_hist_size;
1770 if ((h = _ncl_hist_tail) != NULL) {
1771 if ((_ncl_hist_tail = h->younger) == NULL)
1772 _ncl_hist = NULL;
1773 else
1774 _ncl_hist_tail->older = NULL;
1775 free(h);
1779 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l +1);
1780 h->len = l;
1781 memcpy(h->dat, s, l +1);
1782 jleave:
1783 if ((h->older = _ncl_hist) != NULL)
1784 _ncl_hist->younger = h;
1785 else
1786 _ncl_hist_tail = h;
1787 h->younger = NULL;
1788 _ncl_hist = h;
1790 rele_all_sigs();
1791 j_leave:
1792 # endif
1793 UNUSED(s);
1796 # ifdef HAVE_HISTORY
1797 FL int
1798 c_history(void *v)
1800 C_HISTORY_SHARED;
1802 jlist: {
1803 FILE *fp;
1804 char *cp;
1805 size_t i, b;
1806 struct hist *h;
1808 if (_ncl_hist == NULL)
1809 goto jleave;
1811 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1812 perror("tmpfile");
1813 v = NULL;
1814 goto jleave;
1816 rm(cp);
1817 Ftfree(&cp);
1819 i = _ncl_hist_size;
1820 b = 0;
1821 for (h = _ncl_hist; h != NULL; --i, b += h->len, h = h->older)
1822 fprintf(fp, "%4lu. %-50.50s (%4lu+%lu bytes)\n",
1823 (ul_it)i, h->dat, (ul_it)b, (ul_it)h->len);
1825 page_or_print(fp, i);
1826 Fclose(fp);
1828 goto jleave;
1830 jclear: {
1831 struct hist *h;
1832 while ((h = _ncl_hist) != NULL) {
1833 _ncl_hist = h->older;
1834 free(h);
1836 _ncl_hist_tail = NULL;
1837 _ncl_hist_size = 0;
1839 goto jleave;
1841 jentry: {
1842 struct hist *h = _ncl_hist;
1843 if (UICMP(z, entry, <=, _ncl_hist_size)) {
1844 entry = (long)_ncl_hist_size - entry;
1845 for (h = _ncl_hist;; h = h->older)
1846 if (h == NULL)
1847 break;
1848 else if (entry-- != 0)
1849 continue;
1850 else {
1851 v = temporary_arg_v_store = h->dat;
1852 goto jleave;
1855 v = NULL;
1857 goto jleave;
1859 # endif /* HAVE_HISTORY */
1860 #endif /* HAVE_NCL */
1863 * The really-nothing-at-all implementation
1866 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
1867 FL void
1868 tty_init(void)
1871 FL void
1872 tty_destroy(void)
1875 FL void
1876 tty_signal(int sig)
1878 UNUSED(sig);
1881 FL int
1882 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1883 SMALLOC_DEBUG_ARGS)
1886 * TODO The nothing-at-all tty layer even forces re-entering all the
1887 * TODO original data when re-editing a field
1889 bool_t doffl = FAL0;
1891 if (prompt != NULL && *prompt != '\0') {
1892 fputs(prompt, stdout);
1893 doffl = TRU1;
1895 if (n > 0) {
1896 fprintf(stdout, tr(511, "{former content: %.*s} "), (int)n, *linebuf);
1897 n = 0;
1898 doffl = TRU1;
1900 if (doffl)
1901 fflush(stdout);
1902 return (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
1905 FL void
1906 tty_addhist(char const *s)
1908 UNUSED(s);
1910 #endif /* nothing at all */
1912 /* vim:set fenc=utf-8:s-it-mode */