INSTALL: more on v14.5
[s-mailx.git] / tty.c
blob2c3ce6f0d37ff8cc047de42f19e5b4d7e6c717c9
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 - 2013 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 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 = voption("NAIL_HISTFILE");\
74 if ((S) != NULL)\
75 S = fexpand(S, FEXP_LOCAL);\
76 } while (0)
77 # define _CL_HISTSIZE(V) \
78 do {\
79 char const *__sv = voption("NAIL_HISTSIZE");\
80 long __rv;\
81 if (__sv == NULL || *__sv == '\0' ||\
82 (__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)
89 # define _CL_CHECK_ADDHIST(S,NOACT) \
90 do {\
91 switch (*(S)) {\
92 case '\0':\
93 case ' ':\
94 NOACT;\
95 /* FALLTHRU */\
96 default:\
97 break;\
99 } while (0)
100 #endif /* HAVE_HISTORY */
102 /* fexpand() flags for expand-on-tab */
103 #define _CL_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
106 * Because we have multiple identical implementations, change file layout a bit
107 * and place the implementations one after the other below the other externals
110 FL bool_t
111 yorn(char const *msg)
113 char *cp;
115 if (!(options & OPT_INTERACTIVE))
116 return TRU1;
117 do if ((cp = readstr_input(msg, NULL)) == NULL)
118 return FAL0;
119 while (*cp != 'y' && *cp != 'Y' && *cp != 'n' && *cp != 'N');
120 return (*cp == 'y' || *cp == 'Y');
123 FL char *
124 getuser(char const *query)
126 char *user = NULL;
128 if (query == NULL)
129 query = tr(509, "User: ");
131 if (readline_input(LNED_NONE, query, &termios_state.ts_linebuf,
132 &termios_state.ts_linesize) >= 0)
133 user = termios_state.ts_linebuf;
134 termios_state_reset();
135 return user;
138 FL char *
139 getpassword(char const *query) /* FIXME encaps ttystate signal safe */
141 struct termios tios;
142 char *pass = NULL;
144 if (query == NULL)
145 query = tr(510, "Password: ");
146 fputs(query, stdout);
147 fflush(stdout);
150 * FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
151 * foreground pgrp, and can fail with EINTR!!
153 if (options & OPT_TTYIN) {
154 tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
155 memcpy(&tios, &termios_state.ts_tios, sizeof tios);
156 termios_state.ts_needs_reset = TRU1;
157 tios.c_iflag &= ~(ISTRIP);
158 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
159 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
162 if (readline_restart(stdin, &termios_state.ts_linebuf,
163 &termios_state.ts_linesize, 0) >= 0)
164 pass = termios_state.ts_linebuf;
165 termios_state_reset();
167 if (options & OPT_TTYIN)
168 fputc('\n', stdout);
169 return pass;
172 FL bool_t
173 getcredentials(char **user, char **pass)
175 bool_t rv = TRU1;
176 char *u = *user, *p = *pass;
178 if (u == NULL) {
179 if ((u = getuser(NULL)) == NULL)
180 rv = FAL0;
181 else if (p == NULL)
182 u = savestr(u);
183 *user = u;
186 if (p == NULL) {
187 if ((p = getpassword(NULL)) == NULL)
188 rv = FAL0;
189 *pass = p;
191 return rv;
195 * readline(3)
198 #ifdef HAVE_READLINE
199 static sighandler_type _rl_shup;
200 static char * _rl_buf; /* pre_input() hook: initial line */
201 static int _rl_buflen; /* content, and its length */
203 static int _rl_pre_input(void);
205 static int
206 _rl_pre_input(void)
208 /* Handle leftover data from \ escaped former line */
209 rl_extend_line_buffer(_rl_buflen + 10);
210 memcpy(rl_line_buffer, _rl_buf, _rl_buflen + 1);
211 rl_point = rl_end = _rl_buflen;
212 rl_pre_input_hook = (rl_hook_func_t*)NULL;
213 rl_redisplay();
214 return 0;
217 FL void
218 tty_init(void)
220 # ifdef HAVE_HISTORY
221 long hs;
222 char *v;
223 # endif
225 rl_readline_name = UNCONST(uagent);
226 # ifdef HAVE_HISTORY
227 _CL_HISTSIZE(hs);
228 using_history();
229 stifle_history((int)hs);
230 # endif
231 rl_read_init_file(NULL);
233 /* Because rl_read_init_file() may have introduced yet a different
234 * history size limit, simply load and incorporate the history, leave
235 * it up to readline(3) to do the rest */
236 # ifdef HAVE_HISTORY
237 _CL_HISTFILE(v);
238 if (v != NULL)
239 read_history(v);
240 # endif
243 FL void
244 tty_destroy(void)
246 # ifdef HAVE_HISTORY
247 char *v;
249 _CL_HISTFILE(v);
250 if (v != NULL)
251 write_history(v);
252 # endif
256 FL void
257 tty_signal(int sig)
259 sigset_t nset, oset;
261 switch (sig) {
262 # ifdef SIGWINCH
263 case SIGWINCH:
264 break;
265 # endif
266 case SIGHUP:
267 /* readline(3) doesn't catch it :( */
268 rl_free_line_state();
269 rl_cleanup_after_signal();
270 safe_signal(SIGHUP, _rl_shup);
271 sigemptyset(&nset);
272 sigaddset(&nset, sig);
273 sigprocmask(SIG_UNBLOCK, &nset, &oset);
274 kill(0, sig);
275 /* XXX When we come here we'll continue editing, so reestablish
276 * XXX cannot happen */
277 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
278 _rl_shup = safe_signal(SIGHUP, &tty_signal);
279 rl_reset_after_signal();
280 break;
281 default:
282 break;
286 FL int
287 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
288 SMALLOC_DEBUG_ARGS)
290 int nn;
291 char *line;
293 if (n > 0) {
294 _rl_buf = *linebuf;
295 _rl_buflen = (int)n;
296 rl_pre_input_hook = &_rl_pre_input;
299 _rl_shup = safe_signal(SIGHUP, &tty_signal);
300 line = readline(prompt != NULL ? prompt : "");
301 safe_signal(SIGHUP, _rl_shup);
303 if (line == NULL) {
304 nn = -1;
305 goto jleave;
307 n = strlen(line);
309 if (n >= *linesize) {
310 *linesize = LINESIZE + n + 1;
311 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
313 memcpy(*linebuf, line, n);
314 (free)(line);
315 (*linebuf)[n] = '\0';
316 nn = (int)n;
317 jleave:
318 return nn;
321 FL void
322 tty_addhist(char const *s)
324 # ifdef HAVE_HISTORY
325 _CL_CHECK_ADDHIST(s, goto jleave);
326 hold_all_sigs(); /* XXX too heavy */
327 add_history(s); /* XXX yet we jump away! */
328 rele_all_sigs(); /* XXX remove jumps */
329 jleave:
330 # endif
331 UNUSED(s);
333 #endif /* HAVE_READLINE */
336 * BSD editline(3)
339 #ifdef HAVE_EDITLINE
340 static EditLine * _el_el; /* editline(3) handle */
341 static char const * _el_prompt; /* Current prompt */
342 # ifdef HAVE_HISTORY
343 static History * _el_hcom; /* History handle for commline */
344 # endif
346 static char const * _el_getprompt(void);
348 static char const *
349 _el_getprompt(void)
351 return _el_prompt;
354 FL void
355 tty_init(void)
357 # ifdef HAVE_HISTORY
358 HistEvent he;
359 long hs;
360 char *v;
361 # endif
363 # ifdef HAVE_HISTORY
364 _CL_HISTSIZE(hs);
365 _el_hcom = history_init();
366 history(_el_hcom, &he, H_SETSIZE, (int)hs);
367 history(_el_hcom, &he, H_SETUNIQUE, 1);
368 # endif
370 _el_el = el_init(uagent, stdin, stdout, stderr);
371 el_set(_el_el, EL_SIGNAL, 1);
372 el_set(_el_el, EL_TERMINAL, NULL);
373 /* Need to set HIST before EDITOR, otherwise it won't work automatic */
374 # ifdef HAVE_HISTORY
375 el_set(_el_el, EL_HIST, &history, _el_hcom);
376 # endif
377 el_set(_el_el, EL_EDITOR, "emacs");
378 # ifdef EL_PROMPT_ESC
379 el_set(_el_el, EL_PROMPT_ESC, &_el_getprompt, '\1');
380 # else
381 el_set(_el_el, EL_PROMPT, &_el_getprompt);
382 # endif
383 # if 0
384 el_set(_el_el, EL_ADDFN, "tab_complete",
385 "editline(3) internal completion function", &_el_file_cpl);
386 el_set(_el_el, EL_BIND, "^I", "tab_complete", NULL);
387 # endif
388 # ifdef HAVE_HISTORY
389 el_set(_el_el, EL_BIND, "^R", "ed-search-prev-history", NULL);
390 # endif
391 el_source(_el_el, NULL); /* Source ~/.editrc */
393 /* Because el_source() may have introduced yet a different history size
394 * limit, simply load and incorporate the history, leave it up to
395 * editline(3) to do the rest */
396 # ifdef HAVE_HISTORY
397 _CL_HISTFILE(v);
398 if (v != NULL)
399 history(_el_hcom, &he, H_LOAD, v);
400 # endif
403 FL void
404 tty_destroy(void)
406 # ifdef HAVE_HISTORY
407 HistEvent he;
408 char *v;
409 # endif
411 el_end(_el_el);
413 # ifdef HAVE_HISTORY
414 _CL_HISTFILE(v);
415 if (v != NULL)
416 history(_el_hcom, &he, H_SAVE, v);
417 history_end(_el_hcom);
418 # endif
421 FL void
422 tty_signal(int sig)
424 switch (sig) {
425 # ifdef SIGWINCH
426 case SIGWINCH:
427 el_resize(_el_el);
428 break;
429 # endif
430 default:
431 break;
435 FL int
436 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
437 SMALLOC_DEBUG_ARGS)
439 int nn;
440 char const *line;
442 _el_prompt = (prompt != NULL) ? prompt : "";
443 if (n > 0)
444 el_push(_el_el, *linebuf);
445 line = el_gets(_el_el, &nn);
447 if (line == NULL) {
448 nn = -1;
449 goto jleave;
451 assert(nn >= 0);
452 n = (size_t)nn;
453 if (n > 0 && line[n - 1] == '\n')
454 nn = (int)--n;
456 if (n >= *linesize) {
457 *linesize = LINESIZE + n + 1;
458 *linebuf = (srealloc)(*linebuf, *linesize SMALLOC_DEBUG_ARGSCALL);
460 memcpy(*linebuf, line, n);
461 (*linebuf)[n] = '\0';
462 jleave:
463 return nn;
466 FL void
467 tty_addhist(char const *s)
469 # ifdef HAVE_HISTORY
470 /* Enlarge meaning of unique .. to something that rocks;
471 * xxx unfortunately this is expensive to do with editline(3)
472 * xxx maybe it would be better to hook the ptfs instead? */
473 HistEvent he;
474 int i;
476 _CL_CHECK_ADDHIST(s, goto jleave);
478 hold_all_sigs(); /* XXX too heavy, yet we jump away! */
479 if (history(_el_hcom, &he, H_GETUNIQUE) < 0 || he.num == 0)
480 goto jadd;
482 for (i = history(_el_hcom, &he, H_FIRST); i >= 0;
483 i = history(_el_hcom, &he, H_NEXT))
484 if (strcmp(he.str, s) == 0) {
485 history(_el_hcom, &he, H_DEL, he.num);
486 break;
488 jadd:
489 history(_el_hcom, &he, H_ENTER, s);
490 rele_all_sigs(); /* XXX remove jumps */
491 jleave:
492 # endif
493 UNUSED(s);
495 #endif /* HAVE_EDITLINE */
498 * NCL: our homebrew version (inspired from NetBSD sh(1) / dash(1)s hetio.c).
500 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
501 * We do not handle character widths because the terminal must deal with that
502 * anyway on the one hand, and also wcwidth(3) doesn't support zero-width
503 * characters by definition on the other. We're addicted.
505 * To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
506 * we're forced to use the very same buffer--the one that is passed through to
507 * us from the outside--to store anything we need, i.e., a `struct cell[]', and
508 * convert that on-the-fly back to the plain char* result once we're done.
509 * To simplify our live, use savestr() buffers for all other needed memory
513 * TODO NCL: don't use that stupid .sint=-1 stuff, but simply block all signals
514 * TODO NCL: during handler de-/installation handling.
517 #ifdef HAVE_NCL
518 # ifndef MAX_INPUT
519 # define MAX_INPUT 255 /* (_POSIX_MAX_INPUT = 255 as of Issue 7 TC1) */
520 # endif
522 /* Since we simply fputs(3) the prompt, assume each character requires two
523 * visual cells -- and we need to restrict the maximum prompt size because
524 * of MAX_INPUT and our desire to have room for some error message left */
525 # define _PROMPT_VLEN(P) (strlen(P) * 2)
526 # define _PROMPT_MAX ((MAX_INPUT / 2) + (MAX_INPUT / 4))
528 union xsighdl {
529 sighandler_type shdl; /* Try avoid races by setting */
530 sl_it sint; /* .sint=-1 when inactive */
532 CTA(sizeof(sl_it) >= sizeof(sighandler_type));
534 struct xtios {
535 struct termios told;
536 struct termios tnew;
539 struct cell {
540 wchar_t wc;
541 ui_it count;
542 char cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
545 struct line {
546 size_t cursor; /* Current cursor position */
547 size_t topins; /* Outermost cursor col set */
548 union {
549 char * cbuf; /* *x_buf */
550 struct cell * cells;
551 } line;
552 struct str defc; /* Current default content */
553 struct str savec; /* Saved default content */
554 # ifdef HAVE_HISTORY
555 struct hist * hist; /* History cursor */
556 # endif
557 char const * prompt;
558 char const * nd; /* Cursor right */
559 char ** x_buf; /* Caller pointers */
560 size_t * x_bufsize;
563 # ifdef HAVE_HISTORY
564 struct hist {
565 struct hist * older;
566 struct hist * younger;
567 size_t len;
568 char dat[VFIELD_SIZE(sizeof(size_t))];
570 # endif
572 static union xsighdl _ncl_oint;
573 static union xsighdl _ncl_oquit;
574 static union xsighdl _ncl_oterm;
575 static union xsighdl _ncl_ohup;
576 static union xsighdl _ncl_otstp;
577 static union xsighdl _ncl_ottin;
578 static union xsighdl _ncl_ottou;
579 static struct xtios _ncl_tios;
580 # ifdef HAVE_HISTORY
581 static struct hist * _ncl_hist;
582 static size_t _ncl_hist_size;
583 static size_t _ncl_hist_size_max;
584 static bool_t _ncl_hist_load;
585 # endif
587 static void _ncl_sigs_up(void);
588 static void _ncl_sigs_down(void);
590 static void _ncl_term_mode(bool_t raw);
592 static void _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS);
593 static void _ncl_bs_eof_dvup(struct cell *cap, size_t i);
594 static ssize_t _ncl_wboundary(struct line *l, ssize_t dir);
595 static ssize_t _ncl_cell2dat(struct line *l);
596 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
597 static void _ncl_cell2save(struct line *l);
598 # endif
600 static void _ncl_khome(struct line *l, bool_t dobell);
601 static void _ncl_kend(struct line *l);
602 static void _ncl_kbs(struct line *l);
603 static void _ncl_kkill(struct line *l, bool_t dobell);
604 static ssize_t _ncl_keof(struct line *l);
605 static void _ncl_kleft(struct line *l);
606 static void _ncl_kright(struct line *l);
607 static void _ncl_krefresh(struct line *l);
608 static void _ncl_kbwddelw(struct line *l);
609 static void _ncl_kgow(struct line *l, ssize_t dir);
610 static void _ncl_kother(struct line *l, wchar_t wc);
611 # ifdef HAVE_HISTORY
612 static size_t __ncl_khist_shared(struct line *l, struct hist *hp);
613 static size_t _ncl_khist(struct line *l, bool_t backwd);
614 static size_t _ncl_krhist(struct line *l);
615 # endif
616 # ifdef HAVE_TABEXPAND
617 static size_t _ncl_kht(struct line *l);
618 # endif
619 static ssize_t _ncl_readline(char const *prompt, char **buf, size_t *bufsize,
620 size_t len SMALLOC_DEBUG_ARGS);
622 static void
623 _ncl_sigs_up(void)
625 if (_ncl_oint.sint == -1)
626 _ncl_oint.shdl = safe_signal(SIGINT, &tty_signal);
627 if (_ncl_oquit.sint == -1)
628 _ncl_oquit.shdl = safe_signal(SIGQUIT, &tty_signal);
629 if (_ncl_oterm.sint == -1)
630 _ncl_oterm.shdl = safe_signal(SIGTERM, &tty_signal);
631 if (_ncl_ohup.sint == -1)
632 _ncl_ohup.shdl = safe_signal(SIGHUP, &tty_signal);
633 if (_ncl_otstp.sint == -1)
634 _ncl_otstp.shdl = safe_signal(SIGTSTP, &tty_signal);
635 if (_ncl_ottin.sint == -1)
636 _ncl_ottin.shdl = safe_signal(SIGTTIN, &tty_signal);
637 if (_ncl_ottou.sint == -1)
638 _ncl_ottou.shdl = safe_signal(SIGTTOU, &tty_signal);
641 static void
642 _ncl_sigs_down(void)
644 /* aaah.. atomic cas would be nice (but isn't it all redundant?) */
645 sighandler_type st;
647 if (_ncl_ottou.sint != -1) {
648 st = _ncl_ottou.shdl, _ncl_ottou.sint = -1;
649 safe_signal(SIGTTOU, st);
651 if (_ncl_ottin.sint != -1) {
652 st = _ncl_ottin.shdl, _ncl_ottin.sint = -1;
653 safe_signal(SIGTTIN, st);
655 if (_ncl_otstp.sint != -1) {
656 st = _ncl_otstp.shdl, _ncl_otstp.sint = -1;
657 safe_signal(SIGTSTP, st);
659 if (_ncl_ohup.sint != -1) {
660 st = _ncl_ohup.shdl, _ncl_ohup.sint = -1;
661 safe_signal(SIGHUP, st);
663 if (_ncl_oterm.sint != -1) {
664 st = _ncl_oterm.shdl, _ncl_oterm.sint = -1;
665 safe_signal(SIGTERM, st);
667 if (_ncl_oquit.sint != -1) {
668 st = _ncl_oquit.shdl, _ncl_oquit.sint = -1;
669 safe_signal(SIGQUIT, st);
671 if (_ncl_oint.sint != -1) {
672 st = _ncl_oint.shdl, _ncl_oint.sint = -1;
673 safe_signal(SIGINT, st);
677 static void
678 _ncl_term_mode(bool_t raw)
680 struct termios *tiosp = &_ncl_tios.told;
682 if (!raw)
683 goto jleave;
685 /* Always requery the attributes, in case we've been moved from background
686 * to foreground or however else in between sessions */
687 tcgetattr(STDIN_FILENO, tiosp);
688 memcpy(&_ncl_tios.tnew, tiosp, sizeof *tiosp);
689 tiosp = &_ncl_tios.tnew;
690 tiosp->c_cc[VMIN] = 1;
691 tiosp->c_cc[VTIME] = 0;
692 tiosp->c_iflag &= ~(ISTRIP);
693 tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
694 jleave:
695 tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
698 static void
699 _ncl_check_grow(struct line *l, size_t no SMALLOC_DEBUG_ARGS)
701 size_t i = (l->topins + no) * sizeof(struct cell) + 2 * sizeof(struct cell);
703 if (i > *l->x_bufsize) {
704 i <<= 1;
705 *l->x_bufsize = i;
706 l->line.cbuf =
707 *l->x_buf = (srealloc_safe)(*l->x_buf, i SMALLOC_DEBUG_ARGSCALL);
711 static void
712 _ncl_bs_eof_dvup(struct cell *cap, size_t i)
714 size_t j;
716 if (i > 0)
717 memmove(cap, cap + 1, i * sizeof(*cap));
719 /* And.. the (rest of the) visual update */
720 for (j = 0; j < i; ++j)
721 fwrite(cap[j].cbuf, sizeof *cap->cbuf, cap[j].count, stdout);
722 fputs(" \b", stdout);
723 for (j = 0; j < i; ++j)
724 putchar('\b');
727 static ssize_t
728 _ncl_wboundary(struct line *l, ssize_t dir)
730 size_t c = l->cursor, t = l->topins, i;
731 struct cell *cap;
732 bool_t anynon;
734 i = (size_t)-1;
735 if (dir < 0) {
736 if (c == 0)
737 goto jleave;
738 } else if (c == t)
739 goto jleave;
740 else
741 --t, --c; /* Unsigned wrapping may occur (twice), then */
743 for (i = 0, cap = l->line.cells, anynon = FAL0;;) {
744 wchar_t wc = cap[c + dir].wc;
745 if (iswblank(wc) || iswpunct(wc)) {
746 if (anynon)
747 break;
748 } else
749 anynon = TRU1;
750 ++i;
751 c += dir;
752 if (dir < 0) {
753 if (c == 0)
754 break;
755 } else if (c == t)
756 break;
758 jleave:
759 return (ssize_t)i;
762 static ssize_t
763 _ncl_cell2dat(struct line *l)
765 size_t len = 0, i;
767 if (l->topins > 0)
768 for (i = 0; i < l->topins; ++i) {
769 struct cell *cap = l->line.cells + i;
770 memcpy(l->line.cbuf + len, cap->cbuf, cap->count);
771 len += cap->count;
773 l->line.cbuf[len] = '\0';
774 return (ssize_t)len;
777 # if defined HAVE_HISTORY || defined HAVE_TABEXPAND
778 static void
779 _ncl_cell2save(struct line *l)
781 size_t len, i;
782 struct cell *cap;
784 l->savec.s = NULL, l->savec.l = 0;
785 if (l->topins == 0)
786 goto jleave;
788 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i)
789 len += cap->count;
791 l->savec.l = len;
792 l->savec.s = salloc(len + 1);
794 for (cap = l->line.cells, len = i = 0; i < l->topins; ++cap, ++i) {
795 memcpy(l->savec.s + len, cap->cbuf, cap->count);
796 len += cap->count;
798 l->savec.s[len] = '\0';
799 jleave:
802 # endif
804 static void
805 _ncl_khome(struct line *l, bool_t dobell)
807 size_t c = l->cursor;
809 if (c > 0) {
810 l->cursor = 0;
811 while (c-- != 0)
812 putchar('\b');
813 } else if (dobell)
814 putchar('\a');
817 static void
818 _ncl_kend(struct line *l)
820 ssize_t i = (ssize_t)(l->topins - l->cursor);
822 if (i > 0) {
823 l->cursor = l->topins;
824 while (i-- != 0)
825 fputs(l->nd, stdout);
826 } else
827 putchar('\a');
830 static void
831 _ncl_kbs(struct line *l)
833 ssize_t c = l->cursor, t = l->topins;
835 if (c > 0) {
836 putchar('\b');
837 l->cursor = --c;
838 l->topins = --t;
839 t -= c;
840 _ncl_bs_eof_dvup(l->line.cells + c, t);
841 } else
842 putchar('\a');
845 static void
846 _ncl_kkill(struct line *l, bool_t dobell)
848 size_t j, c = l->cursor, i = (size_t)(l->topins - c);
850 if (i > 0) {
851 l->topins = c;
852 for (j = i; j != 0; --j)
853 putchar(' ');
854 for (j = i; j != 0; --j)
855 putchar('\b');
856 } else if (dobell)
857 putchar('\a');
860 static ssize_t
861 _ncl_keof(struct line *l)
863 size_t c = l->cursor, t = l->topins;
864 ssize_t i = (ssize_t)(t - c);
866 if (i > 0) {
867 l->topins = --t;
868 _ncl_bs_eof_dvup(l->line.cells + c, --i);
869 } else if (t == 0 && ! boption("ignoreeof")) {
870 fputs("^D", stdout);
871 fflush(stdout);
872 i = -1;
873 } else {
874 putchar('\a');
875 i = 0;
877 return i;
880 static void
881 _ncl_kleft(struct line *l)
883 if (l->cursor > 0) {
884 --l->cursor;
885 putchar('\b');
886 } else
887 putchar('\a');
890 static void
891 _ncl_kright(struct line *l)
893 if (l->cursor < l->topins) {
894 ++l->cursor;
895 fputs(l->nd, stdout);
896 } else
897 putchar('\a');
900 static void
901 _ncl_krefresh(struct line *l)
903 struct cell *cap;
904 size_t i;
906 putchar('\r');
907 if (l->prompt != NULL && *l->prompt != '\0')
908 fputs(l->prompt, stdout);
909 for (cap = l->line.cells, i = l->topins; i > 0; ++cap, --i)
910 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
911 for (i = l->topins - l->cursor; i > 0; --i)
912 putchar('\b');
915 static void
916 _ncl_kbwddelw(struct line *l)
918 ssize_t i;
919 size_t c = l->cursor, t, j;
920 struct cell *cap;
922 i = _ncl_wboundary(l, -1);
923 if (i <= 0) {
924 if (i < 0)
925 putchar('\a');
926 goto jleave;
929 c = l->cursor - i;
930 t = l->topins;
931 l->topins = t - i;
932 l->cursor = c;
933 cap = l->line.cells + c;
935 if (t != l->cursor) {
936 j = t - c + i;
937 memmove(cap, cap + i, j * sizeof(*cap));
940 for (j = i; j > 0; --j)
941 putchar('\b');
942 for (j = l->topins - c; j > 0; ++cap, --j)
943 fwrite(cap[0].cbuf, sizeof *cap->cbuf, cap[0].count, stdout);
944 for (j = i; j > 0; --j)
945 putchar(' ');
946 for (j = t - c; j > 0; --j)
947 putchar('\b');
948 jleave:
952 static void
953 _ncl_kgow(struct line *l, ssize_t dir)
955 ssize_t i = _ncl_wboundary(l, dir);
956 if (i <= 0) {
957 if (i < 0)
958 putchar('\a');
959 goto jleave;
962 if (dir < 0) {
963 l->cursor -= i;
964 while (i-- > 0)
965 putchar('\b');
966 } else {
967 l->cursor += i;
968 while (i-- > 0)
969 fputs(l->nd, stdout);
971 jleave:
975 static void
976 _ncl_kother(struct line *l, wchar_t wc)
978 /* Append if at EOL, insert otherwise;
979 * since we may move around character-wise, always use a fresh ps */
980 mbstate_t ps;
981 struct cell cell, *cap;
982 size_t i, c;
984 /* First init a cell and see wether we'll really handle this wc */
985 cell.wc = wc;
986 memset(&ps, 0, sizeof ps);
987 i = wcrtomb(cell.cbuf, wc, &ps);
988 if (i > MB_LEN_MAX)
989 goto jleave;
990 cell.count = (ui_it)i;
991 if (enc_has_state) {
992 i = wcrtomb(cell.cbuf + i, L'\0', &ps);
993 if (i == 1)
995 else if (--i < MB_LEN_MAX)
996 cell.count += (ui_it)i;
997 else
998 goto jleave;
1001 /* Yes, we will! Place it in the array */
1002 c = l->cursor++;
1003 i = l->topins++ - c;
1004 cap = l->line.cells + c;
1005 if (i > 0)
1006 memmove(cap + 1, cap, i * sizeof(cell));
1007 memcpy(cap, &cell, sizeof cell);
1009 /* And update visual */
1010 c = i;
1012 fwrite(cap->cbuf, sizeof *cap->cbuf, cap->count, stdout);
1013 while ((++cap, i-- != 0));
1014 while (c-- != 0)
1015 putchar('\b');
1016 jleave:
1020 # ifdef HAVE_HISTORY
1021 static size_t
1022 __ncl_khist_shared(struct line *l, struct hist *hp)
1024 size_t rv;
1026 if ((l->hist = hp) != NULL) {
1027 l->defc.s = savestrbuf(hp->dat, hp->len);
1028 rv =
1029 l->defc.l = hp->len;
1030 if (l->topins > 0) {
1031 _ncl_khome(l, FAL0);
1032 _ncl_kkill(l, FAL0);
1034 } else {
1035 putchar('\a');
1036 rv = 0;
1038 return rv;
1041 static size_t
1042 _ncl_khist(struct line *l, bool_t backwd)
1044 struct hist *hp;
1046 /* If we're not in history mode yet, save line content;
1047 * also, disallow forward search, then, and, of course, bail unless we
1048 * do have any history at all */
1049 if ((hp = l->hist) == NULL) {
1050 if (! backwd)
1051 goto jleave;
1052 if ((hp = _ncl_hist) == NULL)
1053 goto jleave;
1054 _ncl_cell2save(l);
1055 goto jleave;
1058 hp = backwd ? hp->older : hp->younger;
1059 jleave:
1060 return __ncl_khist_shared(l, hp);
1063 static size_t
1064 _ncl_krhist(struct line *l)
1066 struct str orig_savec;
1067 struct hist *hp = NULL;
1069 /* We cannot complete an empty line */
1070 if (l->topins == 0) {
1071 /* XXX The upcoming hard reset would restore a set savec buffer,
1072 * XXX so forcefully reset that. A cleaner solution would be to
1073 * XXX reset it whenever a restore is no longer desired */
1074 l->savec.s = NULL, l->savec.l = 0;
1075 goto jleave;
1077 if ((hp = l->hist) == NULL) {
1078 if ((hp = _ncl_hist) == NULL)
1079 goto jleave;
1080 orig_savec.s = NULL;
1081 orig_savec.l = 0; /* silence CC */
1082 } else if ((hp = hp->older) == NULL)
1083 goto jleave;
1084 else
1085 orig_savec = l->savec;
1087 if (orig_savec.s == NULL)
1088 _ncl_cell2save(l);
1089 for (; hp != NULL; hp = hp->older)
1090 if (is_prefix(l->savec.s, hp->dat))
1091 break;
1092 if (orig_savec.s != NULL)
1093 l->savec = orig_savec;
1094 jleave:
1095 return __ncl_khist_shared(l, hp);
1097 # endif
1099 # ifdef HAVE_TABEXPAND
1100 static size_t
1101 _ncl_kht(struct line *l)
1103 struct str orig, bot, topp, sub, exp;
1104 struct cell *cword, *ctop, *cx;
1105 bool_t set_savec = FAL0;
1106 size_t rv = 0;
1108 /* We cannot expand an empty line */
1109 if (l->topins == 0)
1110 goto jleave;
1112 /* Get plain line data; if this is the first expansion/xy, update the
1113 * very original content so that ^G gets the origin back */
1114 orig = l->savec;
1115 _ncl_cell2save(l);
1116 exp = l->savec;
1117 if (orig.s != NULL)
1118 l->savec = orig;
1119 else
1120 set_savec = TRU1;
1121 orig = exp;
1123 cword = l->line.cells;
1124 ctop = cword + l->cursor;
1126 /* topp: separate data right of cursor */
1127 if ((cx = cword + l->topins) != ctop) {
1128 for (rv = 0; cx > ctop; --cx)
1129 rv += cx->count;
1130 topp.l = rv;
1131 topp.s = orig.s + orig.l - rv;
1132 } else
1133 topp.s = NULL, topp.l = 0;
1135 /* bot, sub: we cannot expand the entire data left of cursor, but only
1136 * the last "word", so separate them */
1137 while (cx > cword && ! iswspace(cx[-1].wc))
1138 --cx;
1139 for (rv = 0; cword < cx; ++cword)
1140 rv += cword->count;
1141 sub =
1142 bot = orig;
1143 bot.l = rv;
1144 sub.s += rv;
1145 sub.l -= rv;
1146 sub.l -= topp.l;
1148 if (sub.l > 0) {
1149 sub.s = savestrbuf(sub.s, sub.l);
1150 /* TODO there is a TODO note upon fexpand() with multi-return;
1151 * TODO if that will change, the if() below can be simplified */
1152 /* Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1153 hold_all_sigs();
1154 exp.s = fexpand(sub.s, _CL_TAB_FEXP_FL);
1155 rele_all_sigs();
1157 if (exp.s != NULL && (exp.l = strlen(exp.s)) > 0 &&
1158 (exp.l != sub.l || strcmp(exp.s, sub.s))) {
1159 /* Cramp expansion length to MAX_INPUT, or 255 if not defined.
1160 * Take care to take *prompt* into account, since we don't know
1161 * anything about it's visual length (fputs(3) is used), simply
1162 * assume each character requires two columns */
1163 /* TODO the problem is that we loose control otherwise; in the best
1164 * TODO case the user can control via ^A and ^K etc., but be safe;
1165 * TODO we cannot simply adjust fexpand() because we don't know how
1166 * TODO that is implemented... The real solution would be to check
1167 * TODO wether we fit on a line, and start a pager if not.
1168 * TODO However, that should be part of a real tab-COMPLETION, then,
1169 * TODO i.e., don't EXPAND, but SHOW COMPLETIONS, page-wise if needed.
1170 * TODO And: MAX_INPUT is dynamic: pathconf(2), _SC_MAX_INPUT */
1171 rv = (l->prompt != NULL) ? _PROMPT_VLEN(l->prompt) : 0;
1172 if (rv + bot.l + exp.l + topp.l >= MAX_INPUT) {
1173 char const e1[] = "[maximum line size exceeded]";
1174 exp.s = UNCONST(e1);
1175 exp.l = sizeof(e1) - 1;
1176 topp.l = 0;
1177 if (rv + bot.l + exp.l >= MAX_INPUT)
1178 bot.l = 0;
1179 if (rv + exp.l >= MAX_INPUT) {
1180 char const e2[] = "[ERR]";
1181 exp.s = UNCONST(e2);
1182 exp.l = sizeof(e2) - 1;
1185 orig.l = bot.l + exp.l + topp.l;
1186 orig.s = salloc(orig.l + 1 + 5);
1187 if ((rv = bot.l) > 0)
1188 memcpy(orig.s, bot.s, rv);
1189 memcpy(orig.s + rv, exp.s, exp.l);
1190 rv += exp.l;
1191 if (topp.l > 0) {
1192 memcpy(orig.s + rv, topp.s, topp.l);
1193 rv += topp.l;
1195 orig.s[rv] = '\0';
1197 l->defc = orig;
1198 _ncl_khome(l, FAL0);
1199 _ncl_kkill(l, FAL0);
1200 goto jleave;
1204 /* If we've provided a default content, but failed to expand, there is
1205 * nothing we can "revert to": drop that default again */
1206 if (set_savec)
1207 l->savec.s = NULL, l->savec.l = 0;
1208 rv = 0;
1209 jleave:
1210 return rv;
1212 # endif /* HAVE_TABEXPAND */
1214 static ssize_t
1215 _ncl_readline(char const *prompt, char **buf, size_t *bufsize, size_t len
1216 SMALLOC_DEBUG_ARGS)
1218 /* We want to save code, yet we may have to incorporate a lines'
1219 * default content and / or default input to switch back to after some
1220 * history movement; let "len > 0" mean "have to display some data
1221 * buffer", and only otherwise read(2) it */
1222 mbstate_t ps[2];
1223 struct line l;
1224 char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
1225 wchar_t wc;
1226 ssize_t rv;
1228 memset(&l, 0, sizeof l);
1229 l.line.cbuf = *buf;
1230 if (len != 0) {
1231 l.defc.s = savestrbuf(*buf, len);
1232 l.defc.l = len;
1234 if ((l.prompt = prompt) != NULL && _PROMPT_VLEN(prompt) > _PROMPT_MAX)
1235 l.prompt = prompt = "?ERR?";
1236 if ((l.nd = voption("line-editor-cursor-right")) == NULL)
1237 l.nd = "\033[C";
1238 l.x_buf = buf;
1239 l.x_bufsize = bufsize;
1241 if (prompt != NULL && *prompt != '\0') {
1242 fputs(prompt, stdout);
1243 fflush(stdout);
1245 jrestart:
1246 memset(ps, 0, sizeof ps);
1247 /* TODO: NCL: we should output the reset sequence when we jrestart:
1248 * TODO: NCL: if we are using a stateful encoding? !
1249 * TODO: NCL: in short: this is not yet well understood */
1250 for (;;) {
1251 _ncl_check_grow(&l, len SMALLOC_DEBUG_ARGSCALL);
1253 /* Normal read(2)? Else buffer-takeover: speed this one up */
1254 if (len == 0)
1255 cbufp =
1256 cbuf = cbuf_base;
1257 else {
1258 assert(l.defc.l > 0 && l.defc.s != NULL);
1259 cbufp =
1260 cbuf = l.defc.s + (l.defc.l - len);
1261 cbufp += len;
1264 /* Read in the next complete multibyte character */
1265 for (;;) {
1266 if (len == 0) {
1267 if ((rv = read(STDIN_FILENO, cbufp, 1)) < 1) {
1268 if (errno == EINTR) /* xxx #if !SA_RESTART ? */
1269 continue;
1270 goto jleave;
1272 ++cbufp;
1275 /* Ach! the ISO C multibyte handling!
1276 * Encodings with locking shift states cannot really be helped, since
1277 * it is impossible to only query the shift state, as opposed to the
1278 * entire shift state + character pair (via ISO C functions) */
1279 rv = (ssize_t)mbrtowc(&wc, cbuf, (size_t)(cbufp - cbuf), ps + 0);
1280 if (rv <= 0) {
1281 /* Any error during take-over can only result in a hard reset;
1282 * Otherwise, if it's a hard error, or if too many redundant shift
1283 * sequences overflow our buffer, also perform a hard reset */
1284 if (len != 0 || rv == -1 ||
1285 sizeof cbuf_base == (size_t)(cbufp - cbuf)) {
1286 l.savec.s = l.defc.s = NULL,
1287 l.savec.l = l.defc.l = len = 0;
1288 putchar('\a');
1289 wc = 'G';
1290 goto jreset;
1292 /* Otherwise, due to the way we deal with the buffer, we need to
1293 * restore the mbstate_t from before this conversion */
1294 ps[0] = ps[1];
1295 continue;
1298 if (len != 0 && (len -= (size_t)rv) == 0)
1299 l.defc.s = NULL, l.defc.l = 0;
1300 ps[1] = ps[0];
1301 break;
1304 /* Don't interpret control bytes during buffer take-over */
1305 if (cbuf != cbuf_base)
1306 goto jprint;
1307 switch (wc) {
1308 case 'A' ^ 0x40: /* cursor home */
1309 _ncl_khome(&l, TRU1);
1310 break;
1311 case 'B' ^ 0x40: /* backward character */
1312 _ncl_kleft(&l);
1313 break;
1314 /* 'C': interrupt (CTRL-C) */
1315 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
1316 if ((rv = _ncl_keof(&l)) < 0)
1317 goto jleave;
1318 break;
1319 case 'E' ^ 0x40: /* end of line */
1320 _ncl_kend(&l);
1321 break;
1322 case 'F' ^ 0x40: /* forward character */
1323 _ncl_kright(&l);
1324 break;
1325 /* 'G' below */
1326 case 'H' ^ 0x40: /* backspace */
1327 case '\177':
1328 _ncl_kbs(&l);
1329 break;
1330 case 'I' ^ 0x40: /* horizontal tab */
1331 # ifdef HAVE_TABEXPAND
1332 if ((len = _ncl_kht(&l)) > 0)
1333 goto jrestart;
1334 # endif
1335 goto jbell;
1336 case 'J' ^ 0x40: /* NL (\n) */
1337 goto jdone;
1338 case 'G' ^ 0x40: /* full reset */
1339 jreset:
1340 /* FALLTHRU */
1341 case 'U' ^ 0x40: /* ^U: ^A + ^K */
1342 _ncl_khome(&l, FAL0);
1343 /* FALLTHRU */
1344 case 'K' ^ 0x40: /* kill from cursor to end of line */
1345 _ncl_kkill(&l, (wc == ('K' ^ 0x40) || l.topins == 0));
1346 /* (Handle full reset?) */
1347 if (wc == ('G' ^ 0x40)) {
1348 # ifdef HAVE_HISTORY
1349 l.hist = NULL;
1350 # endif
1351 if ((len = l.savec.l) != 0) {
1352 l.defc = l.savec;
1353 l.savec.s = NULL, l.savec.l = 0;
1354 } else
1355 len = l.defc.l;
1357 fflush(stdout);
1358 goto jrestart;
1359 case 'L' ^ 0x40: /* repaint line */
1360 _ncl_krefresh(&l);
1361 break;
1362 /* 'M': CR (\r) */
1363 case 'N' ^ 0x40: /* history next */
1364 # ifdef HAVE_HISTORY
1365 if (l.hist == NULL)
1366 goto jbell;
1367 if ((len = _ncl_khist(&l, FAL0)) > 0)
1368 goto jrestart;
1369 wc = 'G' ^ 0x40;
1370 goto jreset;
1371 # else
1372 goto jbell;
1373 # endif
1374 /* 'O' */
1375 case 'P' ^ 0x40: /* history previous */
1376 # ifdef HAVE_HISTORY
1377 if ((len = _ncl_khist(&l, TRU1)) > 0)
1378 goto jrestart;
1379 wc = 'G' ^ 0x40;
1380 goto jreset;
1381 # else
1382 goto jbell;
1383 # endif
1384 /* 'Q': no code */
1385 case 'R' ^ 0x40: /* reverse history search */
1386 # ifdef HAVE_HISTORY
1387 if ((len = _ncl_krhist(&l)) > 0)
1388 goto jrestart;
1389 wc = 'G' ^ 0x40;
1390 goto jreset;
1391 # else
1392 goto jbell;
1393 # endif
1394 /* 'S': no code */
1395 /* 'U' above */
1396 /*case 'V' ^ 0x40: TODO*/ /* forward delete "word" */
1397 case 'W' ^ 0x40: /* backward delete "word" */
1398 _ncl_kbwddelw(&l);
1399 break;
1400 case 'X' ^ 0x40: /* move cursor forward "word" */
1401 _ncl_kgow(&l, +1);
1402 break;
1403 case 'Y' ^ 0x40: /* move cursor backward "word" */
1404 _ncl_kgow(&l, -1);
1405 break;
1406 /* 'Z': suspend (CTRL-Z) */
1407 default:
1408 jprint:
1409 if (iswprint(wc)) {
1410 _ncl_kother(&l, wc);
1411 /* Don't clear the history during takeover..
1412 * ..and also avoid fflush()ing unless we've
1413 * worked the entire buffer */
1414 if (len > 0)
1415 continue;
1416 # ifdef HAVE_HISTORY
1417 if (cbuf == cbuf_base)
1418 l.hist = NULL;
1419 # endif
1420 } else {
1421 jbell:
1422 putchar('\a');
1424 break;
1426 fflush(stdout);
1429 /* We have a completed input line, convert the struct cell data to its
1430 * plain character equivalent */
1431 jdone:
1432 putchar('\n');
1433 fflush(stdout);
1434 len = _ncl_cell2dat(&l);
1435 rv = (ssize_t)len;
1436 jleave:
1437 return rv;
1440 FL void
1441 tty_init(void)
1443 # ifdef HAVE_HISTORY
1444 long hs;
1445 char *v, *lbuf;
1446 FILE *f;
1447 size_t lsize, cnt, llen;
1448 # endif
1450 _ncl_oint.sint = _ncl_oquit.sint = _ncl_oterm.sint =
1451 _ncl_ohup.sint = _ncl_otstp.sint = _ncl_ottin.sint =
1452 _ncl_ottou.sint = -1;
1454 # ifdef HAVE_HISTORY
1455 _CL_HISTSIZE(hs);
1456 _ncl_hist_size_max = hs;
1457 if (hs == 0)
1458 goto jleave;
1460 _CL_HISTFILE(v);
1461 if (v == NULL)
1462 goto jleave;
1464 hold_all_sigs(); /* XXX too heavy, yet we may jump even here!? */
1465 f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
1466 if (f == NULL)
1467 goto jdone;
1469 lbuf = NULL;
1470 lsize = 0;
1471 cnt = fsize(f);
1472 while (fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL) {
1473 if (llen > 0 && lbuf[llen - 1] == '\n')
1474 lbuf[--llen] = '\0';
1475 if (llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */
1476 continue;
1477 _ncl_hist_load = TRU1;
1478 tty_addhist(lbuf);
1479 _ncl_hist_load = FAL0;
1481 if (lbuf != NULL)
1482 free(lbuf);
1484 fclose(f);
1485 jdone:
1486 rele_all_sigs(); /* XXX remove jumps */
1487 jleave:
1488 # endif /* HAVE_HISTORY */
1492 FL void
1493 tty_destroy(void)
1495 # ifdef HAVE_HISTORY
1496 long hs;
1497 char *v;
1498 struct hist *hp;
1499 FILE *f;
1501 _CL_HISTSIZE(hs);
1502 if (hs == 0)
1503 goto jleave;
1505 _CL_HISTFILE(v);
1506 if (v == NULL)
1507 goto jleave;
1509 if ((hp = _ncl_hist) != NULL)
1510 while (hp->older != NULL && hs-- != 0)
1511 hp = hp->older;
1513 hold_all_sigs(); /* too heavy, yet we may jump even here!? */
1514 f = fopen(v, "w"); /* TODO temporary + rename?! */
1515 if (f == NULL)
1516 goto jdone;
1517 if (fchmod(fileno(f), S_IRUSR | S_IWUSR) != 0)
1518 goto jclose;
1520 for (; hp != NULL; hp = hp->younger) {
1521 fwrite(hp->dat, sizeof *hp->dat, hp->len, f);
1522 putc('\n', f);
1524 jclose:
1525 fclose(f);
1526 jdone:
1527 rele_all_sigs(); /* XXX remove jumps */
1528 jleave:
1529 # endif /* HAVE_HISTORY */
1533 FL void
1534 tty_signal(int sig)
1536 sigset_t nset, oset;
1538 switch (sig) {
1539 case SIGWINCH:
1540 /* We don't deal with SIGWINCH, yet get called from main.c */
1541 break;
1542 default:
1543 _ncl_term_mode(FAL0);
1544 _ncl_sigs_down();
1545 sigemptyset(&nset);
1546 sigaddset(&nset, sig);
1547 sigprocmask(SIG_UNBLOCK, &nset, &oset);
1548 kill(0, sig);
1549 /* When we come here we'll continue editing, so reestablish */
1550 sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
1551 _ncl_sigs_up();
1552 _ncl_term_mode(TRU1);
1553 break;
1557 FL int
1558 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1559 SMALLOC_DEBUG_ARGS)
1561 ssize_t nn;
1563 /* Of course we have races here, but they cannot be avoided on POSIX
1564 * (except by even *more* actions) */
1565 _ncl_sigs_up();
1566 _ncl_term_mode(TRU1);
1567 nn = _ncl_readline(prompt, linebuf, linesize, n SMALLOC_DEBUG_ARGSCALL);
1568 _ncl_term_mode(FAL0);
1569 _ncl_sigs_down();
1571 return (int)nn;
1574 FL void
1575 tty_addhist(char const *s)
1577 # ifdef HAVE_HISTORY
1578 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
1579 size_t l = strlen(s);
1580 struct hist *h, *o, *y;
1582 _CL_CHECK_ADDHIST(s, goto j_leave);
1584 /* Eliminating duplicates is expensive, but simply inacceptable so
1585 * during the load of a potentially large history file! */
1586 if (! _ncl_hist_load)
1587 for (h = _ncl_hist; h != NULL; h = h->older)
1588 if (h->len == l && strcmp(h->dat, s) == 0) {
1589 hold_all_sigs();
1590 o = h->older;
1591 y = h->younger;
1592 if (o != NULL) {
1593 if ((o->younger = y) == NULL)
1594 _ncl_hist = o;
1596 if (y != NULL)
1597 y->older = o;
1598 else
1599 _ncl_hist = o;
1600 goto jleave;
1602 hold_all_sigs();
1604 if (!_ncl_hist_load && _ncl_hist_size >= _ncl_hist_size_max) {
1605 (h = _ncl_hist->younger
1606 )->older = NULL;
1607 free(_ncl_hist);
1608 _ncl_hist = h;
1611 h = smalloc((sizeof(struct hist) - VFIELD_SIZEOF(struct hist, dat)) + l + 1);
1612 h->len = l;
1613 memcpy(h->dat, s, l + 1);
1614 jleave:
1615 if ((h->older = _ncl_hist) != NULL)
1616 _ncl_hist->younger = h;
1617 h->younger = NULL;
1618 _ncl_hist = h;
1620 rele_all_sigs();
1621 j_leave:
1622 # endif
1623 UNUSED(s);
1625 #endif /* HAVE_NCL */
1628 * The really-nothing-at-all implementation
1631 #if !defined HAVE_READLINE && !defined HAVE_EDITLINE && !defined HAVE_NCL
1632 FL void
1633 tty_init(void)
1636 FL void
1637 tty_destroy(void)
1640 FL void
1641 tty_signal(int sig)
1643 UNUSED(sig);
1646 FL int
1647 (tty_readline)(char const *prompt, char **linebuf, size_t *linesize, size_t n
1648 SMALLOC_DEBUG_ARGS)
1651 * TODO The nothing-at-all tty layer even forces re-entering all the
1652 * TODO original data when re-editing a field
1654 bool_t doffl = FAL0;
1656 if (prompt != NULL && *prompt != '\0') {
1657 fputs(prompt, stdout);
1658 doffl = TRU1;
1660 if (n > 0) {
1661 fprintf(stdout, tr(511, "{former content: %.*s} "), (int)n, *linebuf);
1662 n = 0;
1663 doffl = TRU1;
1665 if (doffl)
1666 fflush(stdout);
1667 return (readline_restart)(stdin, linebuf, linesize,n SMALLOC_DEBUG_ARGSCALL);
1670 FL void
1671 tty_addhist(char const *s)
1673 UNUSED(s);
1675 #endif /* nothing at all */
1677 /* vim:set fenc=utf-8:s-it-mode */