1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ TTY (command line) editing interaction.
3 *@ Because we have multiple line-editor implementations, including our own
4 *@ M(ailx) L(ine) E(ditor), change the file layout a bit and place those
5 *@ one after the other below the other externals.
7 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
9 * Permission to use, copy, modify, and/or distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 #ifndef HAVE_AMALGAMATION
29 # include <readline/readline.h>
31 # include <readline/history.h>
35 #if defined HAVE_READLINE || defined HAVE_MLE || defined HAVE_TERMCAP
36 # define a_TTY_SIGNALS
39 /* Shared history support macros */
41 # define a_TTY_HISTFILE(S) \
43 char const *__hist_obsolete = ok_vlook(NAIL_HISTFILE);\
44 if(__hist_obsolete != NULL)\
45 OBSOLETE(_("please use *history-file* instead of *NAIL_HISTFILE*"));\
46 S = ok_vlook(history_file);\
48 (S) = __hist_obsolete;\
50 S = fexpand(S, FEXP_LOCAL | FEXP_NSHELL);\
53 # define a_TTY_HISTSIZE(V) \
55 char const *__hist_obsolete = ok_vlook(NAIL_HISTSIZE);\
56 char const *__sv = ok_vlook(history_size);\
58 if(__hist_obsolete != NULL)\
59 OBSOLETE(_("please use *history-size* instead of *NAIL_HISTSIZE*"));\
61 __sv = __hist_obsolete;\
62 if(__sv == NULL || (__rv = strtol(__sv, NULL, 10)) == 0)\
70 # define a_TTY_CHECK_ADDHIST(S,NOACT) \
82 # define C_HISTORY_SHARED \
91 if(!asccasecmp(*argv, "show"))\
93 if(!asccasecmp(*argv, "clear"))\
95 if((entry = strtol(*argv, argv, 10)) > 0 && **argv == '\0')\
98 n_err(_("Synopsis: history: %s\n" \
99 "<show> (default), <clear> or select <NO> from editor history"));\
103 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
104 #endif /* HAVE_HISTORY */
107 static sighandler_type a_tty_oint
, a_tty_oquit
, a_tty_oterm
,
109 a_tty_otstp
, a_tty_ottin
, a_tty_ottou
;
113 static void a_tty_sigs_up(void), a_tty_sigs_down(void);
124 sigprocmask(SIG_BLOCK
, &nset
, &oset
);
125 a_tty_oint
= safe_signal(SIGINT
, &n_tty_signal
);
126 a_tty_oquit
= safe_signal(SIGQUIT
, &n_tty_signal
);
127 a_tty_oterm
= safe_signal(SIGTERM
, &n_tty_signal
);
128 a_tty_ohup
= safe_signal(SIGHUP
, &n_tty_signal
);
129 a_tty_otstp
= safe_signal(SIGTSTP
, &n_tty_signal
);
130 a_tty_ottin
= safe_signal(SIGTTIN
, &n_tty_signal
);
131 a_tty_ottou
= safe_signal(SIGTTOU
, &n_tty_signal
);
132 sigprocmask(SIG_SETMASK
, &oset
, NULL
);
137 a_tty_sigs_down(void){
143 sigprocmask(SIG_BLOCK
, &nset
, &oset
);
144 safe_signal(SIGINT
, a_tty_oint
);
145 safe_signal(SIGQUIT
, a_tty_oquit
);
146 safe_signal(SIGTERM
, a_tty_oterm
);
147 safe_signal(SIGHUP
, a_tty_ohup
);
148 safe_signal(SIGTSTP
, a_tty_otstp
);
149 safe_signal(SIGTTIN
, a_tty_ottin
);
150 safe_signal(SIGTTOU
, a_tty_ottou
);
151 sigprocmask(SIG_SETMASK
, &oset
, NULL
);
154 #endif /* a_TTY_SIGNALS */
156 static sigjmp_buf a_tty__actjmp
; /* TODO someday, we won't need it no more */
158 a_tty__acthdl(int s
) /* TODO someday, we won't need it no more */
160 NYD_X
; /* Signal handler */
161 termios_state_reset();
162 siglongjmp(a_tty__actjmp
, s
);
166 getapproval(char const * volatile prompt
, bool_t noninteract_default
)
168 sighandler_type
volatile oint
, ohup
;
173 if (!(options
& OPT_INTERACTIVE
)) {
175 rv
= noninteract_default
;
181 char const *quest
= noninteract_default
182 ? _("[yes]/no? ") : _("[no]/yes? ");
185 prompt
= _("Continue");
186 prompt
= savecatsep(prompt
, ' ', quest
);
189 oint
= safe_signal(SIGINT
, SIG_IGN
);
190 ohup
= safe_signal(SIGHUP
, SIG_IGN
);
191 if ((sig
= sigsetjmp(a_tty__actjmp
, 1)) != 0)
193 safe_signal(SIGINT
, &a_tty__acthdl
);
194 safe_signal(SIGHUP
, &a_tty__acthdl
);
196 if (n_lex_input(prompt
, TRU1
, &termios_state
.ts_linebuf
,
197 &termios_state
.ts_linesize
, NULL
) >= 0)
198 rv
= (boolify(termios_state
.ts_linebuf
, UIZ_MAX
,
199 noninteract_default
) > 0);
201 termios_state_reset();
203 safe_signal(SIGHUP
, ohup
);
204 safe_signal(SIGINT
, oint
);
214 getuser(char const * volatile query
) /* TODO v15-compat obsolete */
216 sighandler_type
volatile oint
, ohup
;
217 char * volatile user
= NULL
;
224 oint
= safe_signal(SIGINT
, SIG_IGN
);
225 ohup
= safe_signal(SIGHUP
, SIG_IGN
);
226 if ((sig
= sigsetjmp(a_tty__actjmp
, 1)) != 0)
228 safe_signal(SIGINT
, &a_tty__acthdl
);
229 safe_signal(SIGHUP
, &a_tty__acthdl
);
231 if (n_lex_input(query
, TRU1
, &termios_state
.ts_linebuf
,
232 &termios_state
.ts_linesize
, NULL
) >= 0)
233 user
= termios_state
.ts_linebuf
;
235 termios_state_reset();
237 safe_signal(SIGHUP
, ohup
);
238 safe_signal(SIGINT
, oint
);
246 getpassword(char const *query
)
248 sighandler_type
volatile oint
, ohup
;
250 char * volatile pass
= NULL
;
255 query
= _("Password: ");
256 fputs(query
, stdout
);
259 /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
260 * FIXME foreground pgrp, and can fail with EINTR!! also affects
261 * FIXME termios_state_reset() */
262 if (options
& OPT_TTYIN
) {
263 tcgetattr(STDIN_FILENO
, &termios_state
.ts_tios
);
264 memcpy(&tios
, &termios_state
.ts_tios
, sizeof tios
);
265 termios_state
.ts_needs_reset
= TRU1
;
266 tios
.c_iflag
&= ~(ISTRIP
);
267 tios
.c_lflag
&= ~(ECHO
| ECHOE
| ECHOK
| ECHONL
);
270 oint
= safe_signal(SIGINT
, SIG_IGN
);
271 ohup
= safe_signal(SIGHUP
, SIG_IGN
);
272 if ((sig
= sigsetjmp(a_tty__actjmp
, 1)) != 0)
274 safe_signal(SIGINT
, &a_tty__acthdl
);
275 safe_signal(SIGHUP
, &a_tty__acthdl
);
277 if (options
& OPT_TTYIN
)
278 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &tios
);
280 if (readline_restart(stdin
, &termios_state
.ts_linebuf
,
281 &termios_state
.ts_linesize
, 0) >= 0)
282 pass
= termios_state
.ts_linebuf
;
284 termios_state_reset();
285 if (options
& OPT_TTYIN
)
288 safe_signal(SIGHUP
, ohup
);
289 safe_signal(SIGINT
, oint
);
295 #endif /* HAVE_SOCKETS */
302 static char *a_tty_rl_buf
; /* pre_input() hook: initial line */
303 static int a_tty_rl_buflen
; /* content, and its length */
305 /* Our rl_pre_input_hook */
306 static int a_tty_rl_pre_input(void);
309 a_tty_rl_pre_input(void){
311 /* Handle leftover data from \ escaped former line */
312 rl_extend_line_buffer(a_tty_rl_buflen
+ 10);
313 memcpy(rl_line_buffer
, a_tty_rl_buf
, a_tty_rl_buflen
+1);
314 rl_point
= rl_end
= a_tty_rl_buflen
;
315 rl_pre_input_hook
= (rl_hook_func_t
*)NULL
;
329 rl_readline_name
= UNCONST(uagent
);
333 stifle_history((int)hs
);
335 rl_read_init_file(NULL
);
337 /* Because rl_read_init_file() may have introduced yet a different
338 * history size limit, simply load and incorporate the history, leave
339 * it up to readline(3) to do the rest */
364 n_tty_signal(int sig
){
365 NYD_X
; /* Signal handler */
367 /* WINCH comes from main.c */
376 /* readline(3) doesn't catch SIGHUP :( */
378 rl_free_line_state();
379 rl_cleanup_after_signal();
381 n_TERMCAP_SUSPEND(TRU1
);
385 sigaddset(&nset
, sig
);
386 sigprocmask(SIG_UNBLOCK
, &nset
, &oset
);
388 /* When we come here we'll continue editing, so reestablish */
389 sigprocmask(SIG_BLOCK
, &oset
, (sigset_t
*)NULL
);
392 n_TERMCAP_RESUME(TRU1
);
394 rl_reset_after_signal();
401 (n_tty_readline
)(char const *prompt
, char **linebuf
, size_t *linesize
, size_t n
408 a_tty_rl_buf
= *linebuf
;
409 a_tty_rl_buflen
= (int)n
;
410 rl_pre_input_hook
= &a_tty_rl_pre_input
;
414 n_TERMCAP_SUSPEND(FAL0
);
415 line
= readline(prompt
!= NULL
? prompt
: "");
416 n_TERMCAP_RESUME(FAL0
);
426 *linesize
= LINESIZE
+ n
+1;
427 *linebuf
= (srealloc
)(*linebuf
, *linesize SMALLOC_DEBUG_ARGSCALL
);
429 memcpy(*linebuf
, line
, n
);
431 (*linebuf
)[n
] = '\0';
439 n_tty_addhist(char const *s
, bool_t isgabby
){
445 if(isgabby
&& !ok_blook(history_gabby
))
447 a_TTY_CHECK_ADDHIST(s
, goto jleave
);
448 hold_all_sigs(); /* XXX too heavy */
449 add_history(s
); /* XXX yet we jump away! */
450 rele_all_sigs(); /* XXX remove jumps */
467 if((fp
= Ftmp(NULL
, "hist", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
){
468 n_perr(_("tmpfile"), 0);
473 hs
= history_get_history_state();
475 for(i
= (ul_i
)hs
->length
, hl
= hs
->entries
+ i
, b
= 0; i
> 0; --i
){
476 char *cp
= (*--hl
)->line
;
477 size_t sl
= strlen(cp
);
479 fprintf(fp
, "%4lu. %-50.50s (%4lu+%2lu B)\n", i
, cp
, b
, sl
);
483 page_or_print(fp
, (size_t)hs
->length
);
493 HISTORY_STATE
*hs
= history_get_history_state();
495 if(UICMP(z
, entry
, <=, hs
->length
))
496 v
= temporary_arg_v_store
= hs
->entries
[entry
- 1]->line
;
502 # endif /* HAVE_HISTORY */
503 #endif /* HAVE_READLINE */
506 * MLE: the Mailx-Line-Editor, our homebrew editor
507 * (inspired from NetBSD sh(1) / dash(1)s hetio.c).
509 * Only used in interactive mode, simply use STDIN_FILENO as point of interest.
510 * TODO . After I/O layer rewrite, also "output to STDIN_FILENO".
511 * TODO . We work with wide characters, but not for buffer takeovers and
512 * TODO cell2save()ings. This should be changed. For the former the buffer
513 * TODO thus needs to be converted to wide first, and then simply be fed in.
514 * TODO . No BIDI support.
515 * TODO . We repaint too much. To overcome this use the same approach that my
516 * TODO terminal library uses, add a true "virtual screen line" that stores
517 * TODO the actually visible content, keep a notion of "first modified slot"
518 * TODO and "last modified slot" (including "unknown" and "any" specials),
519 * TODO update that virtual instead, then synchronize what has truly changed.
520 * TODO I.e., add an indirection layer.
523 /* To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
524 * we're forced to use the very same buffer--the one that is passed through to
525 * us from the outside--to store anything we need, i.e., a "struct cell[]", and
526 * convert that on-the-fly back to the plain char* result once we're done.
527 * To simplify our live, use savestr() buffers for all other needed memory */
529 /* The maximum size (of a_tty_cell's) in a line */
530 # define a_TTY_LINE_MAX SI32_MAX
532 /* (Some more CTAs around) */
533 n_CTA(a_TTY_LINE_MAX
<= SI32_MAX
,
534 "a_TTY_LINE_MAX larger than SI32_MAX, but the MLE uses 32-bit arithmetic");
536 /* When shall the visual screen be scrolled, in % of usable screen width */
537 # define a_TTY_SCROLL_MARGIN_LEFT 15
538 # define a_TTY_SCROLL_MARGIN_RIGHT 10
540 /* fexpand() flags for expand-on-tab */
541 # define a_TTY_TAB_FEXP_FL (FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
543 /* Columns to ripoff: outermost may not be touched, plus position indicator.
544 * Must thus be at least 1, but should be >= 1+4 to dig the position indicator
545 * that we place (if there is sufficient space) */
546 # define a_TTY_WIDTH_RIPOFF 5
548 enum a_tty_visual_flags
{
550 a_TTY_VF_MOD_CURSOR
= 1<<0, /* Cursor moved */
551 a_TTY_VF_MOD_CONTENT
= 1<<1, /* Content modified */
552 a_TTY_VF_MOD_DIRTY
= 1<<2, /* Needs complete repaint */
553 a_TTY_VF_MOD_SINGLE
= 1<<3, /* TODO Drop when indirection as above comes */
554 a_TTY_VF_REFRESH
= a_TTY_VF_MOD_DIRTY
| a_TTY_VF_MOD_CURSOR
|
555 a_TTY_VF_MOD_CONTENT
| a_TTY_VF_MOD_SINGLE
,
556 a_TTY_VF_BELL
= 1<<8, /* Ring the bell */
557 a_TTY_VF_SYNC
= 1<<9, /* Flush/Sync I/O channel */
559 a_TTY_VF_ALL_MASK
= a_TTY_VF_REFRESH
| a_TTY_VF_BELL
| a_TTY_VF_SYNC
,
560 a_TTY__VF_LAST
= a_TTY_VF_SYNC
565 struct a_tty_hist
*tg_hist
;
566 struct a_tty_hist
*tg_hist_tail
;
568 size_t tg_hist_size_max
;
570 struct termios tg_tios_old
;
571 struct termios tg_tios_new
;
576 ui16_t tc_count
; /* ..of bytes */
577 ui8_t tc_width
; /* Visual width; TAB==UI8_MAX! */
578 bool_t tc_novis
; /* Don't display visually as such (control character) */
579 char tc_cbuf
[MB_LEN_MAX
* 2]; /* .. plus reset shift sequence */
583 /* Caller pointers */
585 size_t *tl_x_bufsize
;
586 /* Input processing */
587 char const *tl_reenter_after_cmd
; /* `bind' cmd to exec, then re-readline */
588 /* Line data / content handling */
589 ui32_t tl_count
; /* ..of a_tty_cell's (<= a_TTY_LINE_MAX) */
590 ui32_t tl_cursor
; /* Current a_tty_cell insertion point */
592 char *cbuf
; /* *.tl_x_buf */
593 struct a_tty_cell
*cells
;
595 struct str tl_defc
; /* Current default content */
596 size_t tl_defc_cursor_byte
; /* Desired position of cursor after takeover */
597 struct str tl_savec
; /* Saved default content */
598 struct str tl_yankbuf
; /* Last yanked data */
600 struct a_tty_hist
*tl_hist
; /* History cursor */
602 ui32_t tl_count_max
; /* ..before buffer needs to grow */
603 /* Visual data representation handling */
604 ui32_t tl_vi_flags
; /* enum a_tty_visual_flags */
605 ui32_t tl_lst_count
; /* .tl_count after last sync */
606 ui32_t tl_lst_cursor
; /* .tl_cursor after last sync */
607 /* TODO Add another indirection layer by adding a tl_phy_line of
608 * TODO a_tty_cell objects, incorporate changes in visual layer,
609 * TODO then check what _really_ has changed, sync those changes only */
610 struct a_tty_cell
const *tl_phy_start
; /* First visible cell, left border */
611 ui32_t tl_phy_cursor
; /* Physical cursor position */
612 bool_t tl_quote_roundtrip
; /* For _kht() expansion */
614 ui32_t tl_prompt_length
; /* Preclassified (TODO needed as a_tty_cell) */
615 ui32_t tl_prompt_width
;
616 char const *tl_prompt
; /* Preformatted prompt (including colours) */
617 /* .tl_pos_buf is a hack */
619 char *tl_pos_buf
; /* mle-position colour-on, [4], reset seq. */
620 char *tl_pos
; /* Address of the [4] */
626 struct a_tty_hist
*th_older
;
627 struct a_tty_hist
*th_younger
;
628 # ifdef HAVE_BYTE_ORDER_LITTLE
629 ui32_t th_isgabby
: 1;
632 # ifndef HAVE_BYTE_ORDER_LITTLE
633 ui32_t th_isgabby
: 1;
635 char th_dat
[VFIELD_SIZE(sizeof(ui32_t
))];
639 static struct a_tty_global a_tty
;
642 static void a_tty_term_mode(bool_t raw
);
644 /* 0-X (2), UI8_MAX == \t / TAB */
645 static ui8_t
a_tty_wcwidth(wchar_t wc
);
647 /* Memory / cell / word generics */
648 static void a_tty_check_grow(struct a_tty_line
*tlp
, ui32_t no
650 static ssize_t
a_tty_cell2dat(struct a_tty_line
*tlp
);
651 static void a_tty_cell2save(struct a_tty_line
*tlp
);
653 /* Save away data bytes of given range (max = non-inclusive) */
654 static void a_tty_yank(struct a_tty_line
*tlp
, struct a_tty_cell
*tcpmin
,
655 struct a_tty_cell
*tcpmax
);
657 /* Ask user for hexadecimal number, interpret as UTF-32 */
658 static wchar_t a_tty_vinuni(struct a_tty_line
*tlp
);
660 /* Visual screen synchronization */
661 static bool_t
a_tty_vi_refresh(struct a_tty_line
*tlp
);
663 /* Search for word boundary, starting at tl_cursor, in "dir"ection (<> 0).
664 * Return <0 when moving is impossible (backward direction but in position 0,
665 * forward direction but in outermost column), and relative distance to
666 * tl_cursor otherwise */
667 static si32_t
a_tty_wboundary(struct a_tty_line
*tlp
, si32_t dir
);
670 static void a_tty_khome(struct a_tty_line
*tlp
, bool_t dobell
);
671 static void a_tty_kend(struct a_tty_line
*tlp
);
672 static void a_tty_kbs(struct a_tty_line
*tlp
);
673 static void a_tty_kkill(struct a_tty_line
*tlp
, bool_t dobell
);
674 static si32_t
a_tty_kdel(struct a_tty_line
*tlp
);
675 static void a_tty_kleft(struct a_tty_line
*tlp
);
676 static void a_tty_kright(struct a_tty_line
*tlp
);
677 static void a_tty_kbwddelw(struct a_tty_line
*tlp
);
678 static void a_tty_kgow(struct a_tty_line
*tlp
, si32_t dir
);
679 static void a_tty_kother(struct a_tty_line
*tlp
, wchar_t wc
);
680 static ui32_t
a_tty_kht(struct a_tty_line
*tlp
);
682 static ui32_t
a_tty__khist_shared(struct a_tty_line
*tlp
,
683 struct a_tty_hist
*thp
);
684 static ui32_t
a_tty_khist(struct a_tty_line
*tlp
, bool_t backwd
);
685 static ui32_t
a_tty_krhist(struct a_tty_line
*tlp
);
689 static ssize_t
a_tty_readline(struct a_tty_line
*tlp
, size_t len
693 a_tty_term_mode(bool_t raw
){
694 struct termios
*tiosp
;
697 tiosp
= &a_tty
.tg_tios_old
;
701 /* Always requery the attributes, in case we've been moved from background
702 * to foreground or however else in between sessions */
703 /* XXX Always enforce ECHO and ICANON in the OLD attributes - do so as long
704 * XXX as we don't properly deal with TTIN and TTOU etc. */
705 tcgetattr(STDIN_FILENO
, tiosp
);
706 tiosp
->c_lflag
|= ECHO
| ICANON
;
708 memcpy(&a_tty
.tg_tios_new
, tiosp
, sizeof *tiosp
);
709 tiosp
= &a_tty
.tg_tios_new
;
710 tiosp
->c_cc
[VMIN
] = 1;
711 tiosp
->c_cc
[VTIME
] = 0;
712 tiosp
->c_iflag
&= ~(ISTRIP
);
713 tiosp
->c_lflag
&= ~(ECHO
/*| ECHOE | ECHONL */| ICANON
| IEXTEN
);
715 tcsetattr(STDIN_FILENO
, TCSADRAIN
, tiosp
);
720 a_tty_wcwidth(wchar_t wc
){
724 /* Special case the backslash at first */
731 rv
= ((i
= wcwidth(wc
)) > 0) ? (ui8_t
)i
: 0;
733 rv
= iswprint(wc
) ? 1 + (wc
>= 0x1100u
) : 0; /* TODO use S-CText */
741 a_tty_check_grow(struct a_tty_line
*tlp
, ui32_t no SMALLOC_DEBUG_ARGS
){
745 if(UNLIKELY((cmax
= tlp
->tl_count
+ no
) > tlp
->tl_count_max
)){
748 i
= cmax
* sizeof(struct a_tty_cell
) + 2 * sizeof(struct a_tty_cell
);
749 if(LIKELY(i
>= *tlp
->tl_x_bufsize
)){
750 hold_all_sigs(); /* XXX v15 drop */
753 *tlp
->tl_x_buf
= (srealloc
)(*tlp
->tl_x_buf
, i SMALLOC_DEBUG_ARGSCALL
);
754 rele_all_sigs(); /* XXX v15 drop */
756 tlp
->tl_count_max
= cmax
;
757 *tlp
->tl_x_bufsize
= i
;
763 a_tty_cell2dat(struct a_tty_line
*tlp
){
769 if(LIKELY((i
= tlp
->tl_count
) > 0)){
770 struct a_tty_cell
const *tcap
;
772 tcap
= tlp
->tl_line
.cells
;
774 memcpy(tlp
->tl_line
.cbuf
+ len
, tcap
->tc_cbuf
, tcap
->tc_count
);
775 len
+= tcap
->tc_count
;
776 }while(++tcap
, --i
> 0);
779 tlp
->tl_line
.cbuf
[len
] = '\0';
785 a_tty_cell2save(struct a_tty_line
*tlp
){
787 struct a_tty_cell
*tcap
;
790 tlp
->tl_savec
.s
= NULL
;
793 if(UNLIKELY(tlp
->tl_count
== 0))
796 for(tcap
= tlp
->tl_line
.cells
, len
= 0, i
= tlp
->tl_count
; i
> 0;
798 len
+= tcap
->tc_count
;
800 tlp
->tl_savec
.s
= salloc((tlp
->tl_savec
.l
= len
) +1);
802 for(tcap
= tlp
->tl_line
.cells
, len
= 0, i
= tlp
->tl_count
; i
> 0;
804 memcpy(tlp
->tl_savec
.s
+ len
, tcap
->tc_cbuf
, tcap
->tc_count
);
805 len
+= tcap
->tc_count
;
807 tlp
->tl_savec
.s
[len
] = '\0';
813 a_tty_yank(struct a_tty_line
*tlp
, struct a_tty_cell
*tcpmin
,
814 struct a_tty_cell
*tcpmax
){
816 struct a_tty_cell
*tcp
;
821 for(tcp
= tcpmin
; tcp
< tcpmax
; ++tcp
)
824 tlp
->tl_yankbuf
.s
= cp
= salloc((tlp
->tl_yankbuf
.l
= l
) +1);
827 for(tcp
= tcpmin
; tcp
< tcpmax
; cp
+= l
, ++tcp
)
828 memcpy(cp
, tcp
->tc_cbuf
, l
= tcp
->tc_count
);
834 a_tty_vinuni(struct a_tty_line
*tlp
){
836 union {size_t i
; long l
;} u
;
842 tlp
->tl_vi_flags
|= a_TTY_VF_MOD_DIRTY
;
843 if(!n_termcap_cmdx(n_TERMCAP_CMD_cr
) ||
844 !n_termcap_cmd(n_TERMCAP_CMD_ce
, 0, -1))
848 struct str
const *cpre
, *csuf
;
850 struct n_colour_pen
*cpen
;
852 cpen
= n_colour_pen_create(n_COLOUR_ID_MLE_PROMPT
, NULL
);
853 if((cpre
= n_colour_pen_to_str(cpen
)) != NULL
)
854 csuf
= n_colour_reset_to_str();
860 printf(_("%sPlease enter Unicode code point:%s "),
861 (cpre
!= NULL
? cpre
->s
: ""), (csuf
!= NULL
? csuf
->s
: ""));
865 buf
[sizeof(buf
) -1] = '\0';
867 if(read(STDIN_FILENO
, &buf
[u
.i
], 1) != 1){
868 if(errno
== EINTR
) /* xxx #if !SA_RESTART ? */
874 if(!hexchar(buf
[u
.i
])){
875 char const emsg
[] = "[0-9a-fA-F]";
877 LCTA(sizeof emsg
<= sizeof(buf
));
878 memcpy(buf
, emsg
, sizeof emsg
);
882 putc(buf
[u
.i
], stdout
);
884 if(++u
.i
== sizeof buf
)
889 u
.l
= strtol(buf
, &eptr
, 16);
890 if(u
.l
<= 0 || u
.l
>= 0x10FFFF/* XXX magic; CText */ || *eptr
!= '\0'){
892 n_err(_("\nInvalid input: %s\n"), buf
);
903 a_tty_vi_refresh(struct a_tty_line
*tlp
){
905 a_TRUE_RV
= a_TTY__VF_LAST
<<1, /* Return value bit */
906 a_HAVE_PROMPT
= a_TTY__VF_LAST
<<2, /* Have a prompt */
907 a_SHOW_PROMPT
= a_TTY__VF_LAST
<<3, /* Shall print the prompt */
908 a_MOVE_CURSOR
= a_TTY__VF_LAST
<<4, /* Move visual cursor for user! */
909 a_LEFT_MIN
= a_TTY__VF_LAST
<<5, /* On left boundary */
910 a_RIGHT_MAX
= a_TTY__VF_LAST
<<6,
911 a_HAVE_POSITION
= a_TTY__VF_LAST
<<7, /* Print the position indicator */
913 /* We carry some flags over invocations (not worth a specific field) */
914 a_VISIBLE_PROMPT
= a_TTY__VF_LAST
<<8, /* The prompt is on the screen */
915 a_PERSIST_MASK
= a_VISIBLE_PROMPT
,
916 a__LAST
= a_PERSIST_MASK
919 ui32_t f
, w
, phy_wid_base
, phy_wid
, phy_base
, phy_cur
, cnt
, lstcur
, cur
,
920 vi_left
, vi_right
, phy_nxtcur
;
921 struct a_tty_cell
const *tccp
, *tcp_left
, *tcp_right
, *tcxp
;
923 n_LCTA(UICMP(64, a__LAST
, <, UI32_MAX
), "Flag bits excess storage datatype");
925 f
= tlp
->tl_vi_flags
;
926 tlp
->tl_vi_flags
= (f
& ~(a_TTY_VF_REFRESH
| a_PERSIST_MASK
)) |
929 if((w
= tlp
->tl_prompt_length
) > 0)
931 f
|= a_HAVE_POSITION
;
933 /* XXX We don't have a OnTerminalResize event (see main.c) yet, so we need
934 * XXX to reevaluate our circumstances over and over again */
935 /* Don't display prompt or position indicator on very small screens */
936 if((phy_wid_base
= (ui32_t
)scrnwidth
) <= a_TTY_WIDTH_RIPOFF
)
937 f
&= ~(a_HAVE_PROMPT
| a_HAVE_POSITION
);
939 phy_wid_base
-= a_TTY_WIDTH_RIPOFF
;
941 /* Disable the prompt if the screen is too small; due to lack of some
942 * indicator simply add a second ripoff */
943 if((f
& a_HAVE_PROMPT
) && w
+ a_TTY_WIDTH_RIPOFF
>= phy_wid_base
)
947 phy_wid
= phy_wid_base
;
949 phy_cur
= tlp
->tl_phy_cursor
;
951 lstcur
= tlp
->tl_lst_cursor
;
953 /* XXX Assume dirty screen if shrunk */
954 if(cnt
< tlp
->tl_lst_count
)
955 f
|= a_TTY_VF_MOD_DIRTY
;
957 /* TODO Without HAVE_TERMCAP, it would likely be much cheaper to simply
958 * TODO always "cr + paint + ce + ch", since ce is simulated via spaces.. */
960 /* Quickshot: if the line is empty, possibly print prompt and out */
962 /* In that special case dirty anything if it seems better */
963 if((f
& a_TTY_VF_MOD_CONTENT
) || tlp
->tl_lst_count
> 0)
964 f
|= a_TTY_VF_MOD_DIRTY
;
966 if((f
& a_TTY_VF_MOD_DIRTY
) && phy_cur
!= 0){
967 if(!n_termcap_cmdx(n_TERMCAP_CMD_cr
))
972 if((f
& (a_TTY_VF_MOD_DIRTY
| a_HAVE_PROMPT
)) ==
973 (a_TTY_VF_MOD_DIRTY
| a_HAVE_PROMPT
)){
974 if(fputs(tlp
->tl_prompt
, stdout
) == EOF
)
976 phy_cur
= tlp
->tl_prompt_width
+ 1;
979 /* May need to clear former line content */
980 if((f
& a_TTY_VF_MOD_DIRTY
) &&
981 !n_termcap_cmd(n_TERMCAP_CMD_ce
, phy_cur
, -1))
984 tlp
->tl_phy_start
= tlp
->tl_line
.cells
;
988 /* Try to get an idea of the visual window */
990 /* Find the left visual boundary */
991 phy_wid
= (phy_wid
>> 1) + (phy_wid
>> 2);
992 if((cur
= tlp
->tl_cursor
) == cnt
)
995 w
= (tcp_left
= tccp
= tlp
->tl_line
.cells
+ cur
)->tc_width
;
996 if(w
== UI8_MAX
) /* TODO yet TAB == SPC */
998 while(tcp_left
> tlp
->tl_line
.cells
){
999 ui16_t cw
= tcp_left
[-1].tc_width
;
1001 if(cw
== UI8_MAX
) /* TODO yet TAB == SPC */
1003 if(w
+ cw
>= phy_wid
)
1010 /* If the left hand side of our visual viewpoint consumes less than half
1011 * of the screen width, show the prompt */
1012 if(tcp_left
== tlp
->tl_line
.cells
)
1015 if((f
& (a_LEFT_MIN
| a_HAVE_PROMPT
)) == (a_LEFT_MIN
| a_HAVE_PROMPT
) &&
1016 w
+ tlp
->tl_prompt_width
< phy_wid
){
1017 phy_base
= tlp
->tl_prompt_width
;
1021 /* Then search for right boundary. We always leave the rightmost column
1022 * empty because some terminals [cw]ould wrap the line if we write into
1023 * that. XXX terminfo(5)/termcap(5) have the semi_auto_right_margin/sam/YE
1024 * XXX capability to indicate this, but we don't look at that */
1025 phy_wid
= phy_wid_base
- phy_base
;
1026 tcp_right
= tlp
->tl_line
.cells
+ cnt
;
1028 while(&tccp
[1] < tcp_right
){
1029 ui16_t cw
= tccp
[1].tc_width
;
1032 if(cw
== UI8_MAX
) /* TODO yet TAB == SPC */
1040 vi_right
= w
- vi_left
;
1042 /* If the complete line including prompt fits on the screen, show prompt */
1043 if(--tcp_right
== tccp
){
1046 /* Since we did brute-force walk also for the left boundary we may end up
1047 * in a situation were anything effectively fits on the screen, including
1048 * the prompt that is, but were we don't recognize this since we
1049 * restricted the search to fit in some visual viewpoint. Therefore try
1050 * again to extend the left boundary to overcome that */
1051 if(!(f
& a_LEFT_MIN
)){
1052 struct a_tty_cell
const *tc1p
= tlp
->tl_line
.cells
;
1053 ui32_t vil1
= vi_left
;
1055 assert(!(f
& a_SHOW_PROMPT
));
1056 w
+= tlp
->tl_prompt_width
;
1057 for(tcxp
= tcp_left
;;){
1058 ui32_t i
= tcxp
[-1].tc_width
;
1060 if(i
== UI8_MAX
) /* TODO yet TAB == SPC */
1074 /*w -= tlp->tl_prompt_width;*/
1078 tccp
= tlp
->tl_line
.cells
+ cur
;
1080 if((f
& (a_LEFT_MIN
| a_RIGHT_MAX
| a_HAVE_PROMPT
| a_SHOW_PROMPT
)) ==
1081 (a_LEFT_MIN
| a_RIGHT_MAX
| a_HAVE_PROMPT
) &&
1082 w
+ tlp
->tl_prompt_width
<= phy_wid
){
1083 phy_wid
-= (phy_base
= tlp
->tl_prompt_width
);
1087 /* Try to avoid repainting the complete line - this is possible if the
1088 * cursor "did not leave the screen" and the prompt status hasn't changed.
1089 * I.e., after clamping virtual viewpoint, compare relation to physical */
1090 if((f
& (a_TTY_VF_MOD_SINGLE
/*FIXME*/ |
1091 a_TTY_VF_MOD_CONTENT
/* xxx */ | a_TTY_VF_MOD_DIRTY
)) ||
1092 (tcxp
= tlp
->tl_phy_start
) == NULL
||
1093 tcxp
> tccp
|| tcxp
<= tcp_right
)
1094 f
|= a_TTY_VF_MOD_DIRTY
;
1096 f
|= a_TTY_VF_MOD_DIRTY
;
1098 struct a_tty_cell
const *tcyp
;
1099 si32_t cur_displace
;
1100 ui32_t phy_lmargin
, phy_rmargin
, fx
, phy_displace
;
1102 phy_lmargin
= (fx
= phy_wid
) / 100;
1103 phy_rmargin
= fx
- (phy_lmargin
* a_TTY_SCROLL_MARGIN_RIGHT
);
1104 phy_lmargin
*= a_TTY_SCROLL_MARGIN_LEFT
;
1105 fx
= (f
& (a_SHOW_PROMPT
| a_VISIBLE_PROMPT
));
1107 if(fx
== 0 || fx
== (a_SHOW_PROMPT
| a_VISIBLE_PROMPT
)){
1113 /* We know what we have to paint, start synchronizing */
1115 assert(phy_cur
== tlp
->tl_phy_cursor
);
1116 assert(phy_wid
== phy_wid_base
- phy_base
);
1117 assert(cnt
== tlp
->tl_count
);
1119 assert(lstcur
== tlp
->tl_lst_cursor
);
1120 assert(tccp
== tlp
->tl_line
.cells
+ cur
);
1122 phy_nxtcur
= phy_base
; /* FIXME only if repaint cpl. */
1124 /* Quickshot: is it only cursor movement within the visible screen? */
1125 if((f
& a_TTY_VF_REFRESH
) == a_TTY_VF_MOD_CURSOR
){
1130 /* To be able to apply some quick jump offs, clear line if possible */
1131 if(f
& a_TTY_VF_MOD_DIRTY
){
1132 /* Force complete clearance and cursor reinitialization */
1133 if(!n_termcap_cmdx(n_TERMCAP_CMD_cr
) ||
1134 !n_termcap_cmd(n_TERMCAP_CMD_ce
, 0, -1))
1136 tlp
->tl_phy_start
= tcp_left
;
1140 if((f
& (a_TTY_VF_MOD_DIRTY
| a_SHOW_PROMPT
)) && phy_cur
!= 0){
1141 if(!n_termcap_cmdx(n_TERMCAP_CMD_cr
))
1146 if(f
& a_SHOW_PROMPT
){
1147 assert(phy_base
== tlp
->tl_prompt_width
);
1148 if(fputs(tlp
->tl_prompt
, stdout
) == EOF
)
1150 phy_cur
= phy_nxtcur
;
1151 f
|= a_VISIBLE_PROMPT
;
1153 f
&= ~a_VISIBLE_PROMPT
;
1155 /* FIXME reposition cursor for paint */
1156 for(w
= phy_nxtcur
; tcp_left
<= tcp_right
; ++tcp_left
){
1159 cw
= tcp_left
->tc_width
;
1161 if(LIKELY(!tcp_left
->tc_novis
)){
1162 if(fwrite(tcp_left
->tc_cbuf
, sizeof *tcp_left
->tc_cbuf
,
1163 tcp_left
->tc_count
, stdout
) != tcp_left
->tc_count
)
1165 }else{ /* XXX Shouldn't be here <-> CText, ui_str.c */
1166 char wbuf
[8]; /* XXX magic */
1168 if(options
& OPT_UNICODE
){
1171 wc
= (ui32_t
)tcp_left
->tc_wc
;
1172 if((wc
& ~0x1Fu
) == 0)
1178 n_utf32_to_utf8(wc
, wbuf
);
1180 wbuf
[0] = '?', wbuf
[1] = '\0';
1182 if(fputs(wbuf
, stdout
) == EOF
)
1187 if(cw
== UI8_MAX
) /* TODO yet TAB == SPC */
1190 if(tcp_left
== tccp
)
1195 /* Write something position marker alike if it doesn't fit on screen */
1196 if((f
& a_HAVE_POSITION
) &&
1197 ((f
& (a_LEFT_MIN
| a_RIGHT_MAX
)) != (a_LEFT_MIN
| a_RIGHT_MAX
) ||
1198 ((f
& a_HAVE_PROMPT
) && !(f
& a_SHOW_PROMPT
)))){
1200 char *posbuf
= tlp
->tl_pos_buf
, *pos
= tlp
->tl_pos
;
1202 char posbuf
[5], *pos
= posbuf
;
1207 if(phy_cur
!= (w
= phy_wid_base
) &&
1208 !n_termcap_cmd(n_TERMCAP_CMD_ch
, phy_cur
= w
, 0))
1212 if((f
& a_LEFT_MIN
) && (!(f
& a_HAVE_PROMPT
) || (f
& a_SHOW_PROMPT
)))
1213 memcpy(pos
, "^.+", 3);
1214 else if(f
& a_RIGHT_MAX
)
1215 memcpy(pos
, ".+$", 3);
1217 /* Theoretical line length limit a_TTY_LINE_MAX, choose next power of
1218 * ten (10 ** 10) to represent 100 percent, since we don't have a macro
1219 * that generates a constant, and i don't trust the standard "u type
1220 * suffix automatically scales" calculate the large number */
1221 static char const itoa
[] = "0123456789";
1223 ui64_t
const fact100
= (ui64_t
)0x3B9ACA00u
* 10u, fact
= fact100
/ 100;
1224 ui32_t i
= (ui32_t
)(((fact100
/ cnt
) * tlp
->tl_cursor
) / fact
);
1225 n_LCTA(a_TTY_LINE_MAX
<= SI32_MAX
, "a_TTY_LINE_MAX too large");
1228 pos
[0] = ' ', pos
[1] = itoa
[i
];
1230 pos
[1] = itoa
[i
% 10], pos
[0] = itoa
[i
/ 10];
1234 if(fputs(posbuf
, stdout
) == EOF
)
1239 /* Users are used to see the cursor right of the point of interest, so we
1240 * need some further adjustments unless in special conditions. Be aware
1241 * that we may have adjusted cur at the beginning, too */
1242 if((cur
= tlp
->tl_cursor
) == 0)
1243 phy_nxtcur
= phy_base
;
1244 else if(cur
!= cnt
){
1245 ui16_t cw
= tccp
->tc_width
;
1247 if(cw
== UI8_MAX
) /* TODO yet TAB == SPC */
1253 if(((f
& a_MOVE_CURSOR
) || phy_nxtcur
!= phy_cur
) &&
1254 !n_termcap_cmd(n_TERMCAP_CMD_ch
, phy_cur
= phy_nxtcur
, 0))
1258 tlp
->tl_vi_flags
|= (f
& a_PERSIST_MASK
);
1259 tlp
->tl_lst_count
= tlp
->tl_count
;
1260 tlp
->tl_lst_cursor
= tlp
->tl_cursor
;
1261 tlp
->tl_phy_cursor
= phy_cur
;
1264 return ((f
& a_TRUE_RV
) != 0);
1271 a_tty_wboundary(struct a_tty_line
*tlp
, si32_t dir
){/* TODO shell token-wise */
1273 struct a_tty_cell
*tcap
;
1278 assert(dir
== 1 || dir
== -1);
1281 cnt
= tlp
->tl_count
;
1282 cur
= tlp
->tl_cursor
;
1287 }else if(cur
+ 1 >= cnt
)
1290 --cnt
, --cur
; /* xxx Unsigned wrapping may occur (twice), then */
1292 for(rv
= 0, tcap
= tlp
->tl_line
.cells
, anynon
= FAL0
;;){
1295 wc
= tcap
[cur
+= (ui32_t
)dir
].tc_wc
;
1296 if(iswblank(wc
) || iswpunct(wc
)){
1307 }else if(cur
+ 1 >= cnt
){
1318 a_tty_khome(struct a_tty_line
*tlp
, bool_t dobell
){
1322 if(LIKELY(tlp
->tl_cursor
> 0)){
1324 f
= a_TTY_VF_MOD_CURSOR
;
1330 tlp
->tl_vi_flags
|= f
;
1335 a_tty_kend(struct a_tty_line
*tlp
){
1339 if(LIKELY(tlp
->tl_cursor
< tlp
->tl_count
)){
1340 tlp
->tl_cursor
= tlp
->tl_count
;
1341 f
= a_TTY_VF_MOD_CURSOR
;
1345 tlp
->tl_vi_flags
|= f
;
1350 a_tty_kbs(struct a_tty_line
*tlp
){
1354 cur
= tlp
->tl_cursor
;
1355 cnt
= tlp
->tl_count
;
1357 if(LIKELY(cur
> 0)){
1358 tlp
->tl_cursor
= --cur
;
1359 tlp
->tl_count
= --cnt
;
1361 if((cnt
-= cur
) > 0){
1362 struct a_tty_cell
*tcap
;
1364 tcap
= tlp
->tl_line
.cells
+ cur
;
1365 memmove(tcap
, &tcap
[1], cnt
*= sizeof(*tcap
));
1367 f
= a_TTY_VF_MOD_CURSOR
| a_TTY_VF_MOD_CONTENT
;
1371 tlp
->tl_vi_flags
|= f
;
1376 a_tty_kkill(struct a_tty_line
*tlp
, bool_t dobell
){
1380 if(LIKELY((i
= tlp
->tl_cursor
) < tlp
->tl_count
)){
1381 struct a_tty_cell
*tcap
;
1383 tcap
= &tlp
->tl_line
.cells
[0];
1384 a_tty_yank(tlp
, &tcap
[i
], &tcap
[tlp
->tl_count
]);
1386 i
= a_TTY_VF_MOD_CONTENT
;
1392 tlp
->tl_vi_flags
|= i
;
1397 a_tty_kdel(struct a_tty_line
*tlp
){
1402 cur
= tlp
->tl_cursor
;
1403 cnt
= tlp
->tl_count
;
1404 i
= (si32_t
)(cnt
- cur
);
1407 tlp
->tl_count
= --cnt
;
1409 if(LIKELY(--i
> 0)){
1410 struct a_tty_cell
*tcap
;
1412 tcap
= &tlp
->tl_line
.cells
[cur
];
1413 memmove(tcap
, &tcap
[1], (ui32_t
)i
* sizeof(*tcap
));
1415 f
= a_TTY_VF_MOD_CONTENT
;
1416 }else if(cnt
== 0 && !ok_blook(ignoreeof
)){
1426 tlp
->tl_vi_flags
|= f
;
1432 a_tty_kleft(struct a_tty_line
*tlp
){
1436 if(LIKELY(tlp
->tl_cursor
> 0)){
1438 f
= a_TTY_VF_MOD_CURSOR
;
1442 tlp
->tl_vi_flags
|= f
;
1447 a_tty_kright(struct a_tty_line
*tlp
){
1451 if(LIKELY((i
= tlp
->tl_cursor
+ 1) <= tlp
->tl_count
)){
1453 i
= a_TTY_VF_MOD_CURSOR
;
1457 tlp
->tl_vi_flags
|= i
;
1462 a_tty_kbwddelw(struct a_tty_line
*tlp
){
1463 struct a_tty_cell
*tcap
;
1468 if(UNLIKELY((i
= a_tty_wboundary(tlp
, -1)) <= 0)){
1469 f
= (i
< 0) ? a_TTY_VF_BELL
: a_TTY_VF_NONE
;
1473 cnt
= tlp
->tl_count
- (ui32_t
)i
;
1474 cur
= tlp
->tl_cursor
- (ui32_t
)i
;
1475 tcap
= &tlp
->tl_line
.cells
[cur
];
1477 a_tty_yank(tlp
, &tcap
[0], &tcap
[i
]);
1479 if((tlp
->tl_count
= cnt
) != (tlp
->tl_cursor
= cur
)){
1481 memmove(&tcap
[0], &tcap
[i
], cnt
* sizeof(*tcap
)); /* FIXME*/
1484 f
= a_TTY_VF_MOD_CURSOR
| a_TTY_VF_MOD_CONTENT
;
1486 tlp
->tl_vi_flags
|= f
;
1491 a_tty_kgow(struct a_tty_line
*tlp
, si32_t dir
){
1496 if(UNLIKELY((i
= a_tty_wboundary(tlp
, dir
)) <= 0))
1497 f
= (i
< 0) ? a_TTY_VF_BELL
: a_TTY_VF_NONE
;
1501 tlp
->tl_cursor
+= (ui32_t
)i
;
1502 f
= a_TTY_VF_MOD_CURSOR
;
1505 tlp
->tl_vi_flags
|= f
;
1510 a_tty_kother(struct a_tty_line
*tlp
, wchar_t wc
){
1511 /* Append if at EOL, insert otherwise;
1512 * since we may move around character-wise, always use a fresh ps */
1514 struct a_tty_cell tc
, *tcap
;
1520 n_LCTA(a_TTY_LINE_MAX
<= SI32_MAX
, "a_TTY_LINE_MAX too large");
1521 if(tlp
->tl_count
+ 1 >= a_TTY_LINE_MAX
){
1522 n_err(_("Stop here, we can't extend line beyond size limit\n"));
1526 /* First init a cell and see wether we'll really handle this wc */
1527 memset(&ps
, 0, sizeof ps
);
1531 l
= wcrtomb(tc
.tc_cbuf
, tc
.tc_wc
= wc
, &ps
);
1532 if(UNLIKELY(l
> MB_LEN_MAX
)){
1534 n_err(_("wcrtomb(3) error: too many multibyte character bytes\n"));
1537 tc
.tc_count
= (ui16_t
)l
;
1539 if(UNLIKELY((options
& OPT_ENC_MBSTATE
) != 0)){
1540 l
= wcrtomb(&tc
.tc_cbuf
[l
], L
'\0', &ps
);
1542 /* Only NUL terminator */;
1543 else if(LIKELY(--l
< MB_LEN_MAX
))
1544 tc
.tc_count
+= (ui16_t
)l
;
1550 /* Yes, we will! Place it in the array */
1551 tc
.tc_novis
= (iswprint(wc
) == 0);
1552 tc
.tc_width
= a_tty_wcwidth(wc
);
1553 /* TODO if(tc.tc_novis && tc.tc_width > 0) */
1555 cur
= tlp
->tl_cursor
++;
1556 cnt
= tlp
->tl_count
++ - cur
;
1557 tcap
= &tlp
->tl_line
.cells
[cur
];
1559 memmove(&tcap
[1], tcap
, cnt
* sizeof(*tcap
));
1560 f
= a_TTY_VF_MOD_CONTENT
;
1562 f
= a_TTY_VF_MOD_SINGLE
;
1563 memcpy(tcap
, &tc
, sizeof *tcap
);
1565 f
|= a_TTY_VF_MOD_CURSOR
;
1567 tlp
->tl_vi_flags
|= f
;
1572 a_tty_kht(struct a_tty_line
*tlp
){
1574 struct str orig
, bot
, topp
, sub
, exp
;
1575 struct n_string shou
, *shoup
;
1576 struct a_tty_cell
*cword
, *ctop
, *cx
;
1577 bool_t wedid
, set_savec
;
1582 shoup
= n_string_creat_auto(&shou
);
1584 /* Get plain line data; if this is the first expansion/xy, update the
1585 * very original content so that ^G gets the origin back */
1586 orig
= tlp
->tl_savec
;
1587 a_tty_cell2save(tlp
);
1588 exp
= tlp
->tl_savec
;
1590 /*tlp->tl_savec = orig;*/
1596 /* Find the word to be expanded */
1598 cword
= tlp
->tl_line
.cells
;
1599 ctop
= cword
+ tlp
->tl_cursor
;
1600 cx
= cword
+ tlp
->tl_count
;
1602 /* topp: separate data right of cursor */
1604 for(rv
= 0; ctop
< cx
; ++ctop
)
1605 rv
+= ctop
->tc_count
;
1607 topp
.s
= orig
.s
+ orig
.l
- rv
;
1608 ctop
= cword
+ tlp
->tl_cursor
;
1610 topp
.s
= NULL
, topp
.l
= 0;
1612 /* Find the shell token that corresponds to the cursor position */
1618 for(; cword
< ctop
; ++cword
)
1619 max
+= cword
->tc_count
;
1620 cword
= tlp
->tl_line
.cells
;
1628 enum n_shexp_state shs
;
1631 shs
= n_shell_parse_token(NULL
, &sub
, n_SHEXP_PARSE_DRYRUN
|
1632 n_SHEXP_PARSE_TRIMSPACE
| n_SHEXP_PARSE_IGNORE_EMPTY
);
1636 assert(max
>= sub
.l
);
1642 if(shs
& n_SHEXP_STATE_ERR_MASK
){
1643 n_err(_("Invalid completion pattern: %.*s\n"),
1647 n_shell_parse_token(shoup
, &exp
,
1648 n_SHEXP_PARSE_TRIMSPACE
| n_SHEXP_PARSE_IGNORE_EMPTY
);
1652 sub
.s
= n_string_cp(shoup
);
1653 sub
.l
= shoup
->s_len
;
1657 /* Leave room for "implicit asterisk" expansion, as below */
1660 sub
.s
= UNCONST("*");
1666 /* TODO Super-Heavy-Metal: block all sigs, avoid leaks on jump */
1668 exp
.s
= fexpand(sub
.s
, a_TTY_TAB_FEXP_FL
);
1671 if(exp
.s
== NULL
|| (exp
.l
= strlen(exp
.s
)) == 0)
1674 /* May be multi-return! */
1675 if(pstate
& PS_EXPAND_MULTIRESULT
)
1678 /* xxx That is not really true since the limit counts characters not bytes */
1679 n_LCTA(a_TTY_LINE_MAX
<= SI32_MAX
, "a_TTY_LINE_MAX too large");
1680 if(exp
.l
+ 1 >= a_TTY_LINE_MAX
){
1681 n_err(_("Tabulator expansion would extend beyond line size limit\n"));
1685 /* If the expansion equals the original string, assume the user wants what
1686 * is usually known as tab completion, append `*' and restart */
1687 if(!wedid
&& exp
.l
== sub
.l
&& !memcmp(exp
.s
, sub
.s
, exp
.l
)){
1688 if(sub
.s
[sub
.l
- 1] == '*')
1692 sub
.s
[sub
.l
++] = '*';
1693 sub
.s
[sub
.l
] = '\0';
1696 /* If it is a directory, and there is not yet a / appended, then we want the
1697 * user to confirm that he wants to dive in -- with only a HT */
1698 else if(wedid
&& exp
.l
== --sub
.l
&& !memcmp(exp
.s
, sub
.s
, exp
.l
) &&
1699 exp
.s
[exp
.l
- 1] != '/'){
1700 if(stat(exp
.s
, &sb
) || !S_ISDIR(sb
.st_mode
))
1702 sub
.s
= salloc(exp
.l
+ 1 +1);
1703 memcpy(sub
.s
, exp
.s
, exp
.l
);
1704 sub
.s
[exp
.l
++] = '/';
1705 sub
.s
[exp
.l
] = '\0';
1710 if(wedid
&& (wedid
= (exp
.s
[exp
.l
- 1] == '*')))
1712 exp
.s
[exp
.l
] = '\0';
1714 exp
.l
= strlen(exp
.s
= n_shell_quote_cp(exp
.s
, tlp
->tl_quote_roundtrip
));
1715 tlp
->tl_defc_cursor_byte
= bot
.l
+ exp
.l
-1;
1720 orig
.l
= bot
.l
+ exp
.l
+ topp
.l
;
1721 orig
.s
= salloc(orig
.l
+ 5 +1);
1722 if((rv
= (ui32_t
)bot
.l
) > 0)
1723 memcpy(orig
.s
, bot
.s
, rv
);
1724 memcpy(orig
.s
+ rv
, exp
.s
, exp
.l
);
1727 memcpy(orig
.s
+ rv
, topp
.s
, topp
.l
);
1732 tlp
->tl_defc
= orig
;
1733 tlp
->tl_count
= tlp
->tl_cursor
= 0;
1734 f
|= a_TTY_VF_MOD_DIRTY
;
1736 n_string_gut(shoup
);
1737 tlp
->tl_vi_flags
|= f
;
1742 struct n_visual_info_ctx vic
;
1747 size_t locolen
, scrwid
, lnlen
, lncnt
, prefixlen
;
1750 if((fp
= Ftmp(NULL
, "tabex", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
){
1751 n_perr(_("tmpfile"), 0);
1755 /* How long is the result string for real? Search the NUL NUL
1756 * terminator. While here, detect the longest entry to perform an
1757 * initial allocation of our accumulator string */
1762 i
= strlen(&exp
.s
[++exp
.l
]);
1763 locolen
= MAX(locolen
, i
);
1765 }while(exp
.s
[exp
.l
+ 1] != '\0');
1767 shoup
= n_string_reserve(n_string_trunc(shoup
, 0),
1768 locolen
+ (locolen
>> 1));
1770 /* Iterate (once again) over all results */
1771 scrwid
= (size_t)scrnwidth
- ((size_t)scrnwidth
>> 3);
1773 UNINIT(prefixlen
, 0);
1774 UNINIT(lococp
, NULL
);
1776 for(isfirst
= TRU1
; exp
.l
> 0; isfirst
= FAL0
, c1
= c2
){
1778 char const *fullpath
;
1782 sub
.l
= i
= strlen(sub
.s
);
1784 if((exp
.l
-= i
) > 0)
1788 /* Separate dirname and basename */
1793 if((cp
= strrchr(fullpath
, '/')) != NULL
)
1794 prefixlen
= PTR2SIZE(++cp
- fullpath
);
1798 if(prefixlen
> 0 && prefixlen
< sub
.l
){
1803 /* We want case-insensitive sort-order */
1804 memset(&vic
, 0, sizeof vic
);
1805 vic
.vic_indat
= sub
.s
;
1806 vic
.vic_inlen
= sub
.l
;
1807 c2
= n_visual_info(&vic
, n_VISUAL_INFO_ONE_CHAR
) ? vic
.vic_waccu
1809 #ifdef HAVE_C90AMEND1
1812 c2
= lowerconv((char)c2
);
1815 /* Query longest common prefix along the way */
1820 }else if(locolen
> 0){
1821 for(i
= 0; i
< locolen
; ++i
)
1822 if(lococp
[i
] != sub
.s
[i
]){
1823 i
= field_detect_clip(i
, lococp
, i
);
1829 /* Prepare display */
1831 shoup
= n_shell_quote(n_string_trunc(shoup
, 0), &input
,
1832 tlp
->tl_quote_roundtrip
);
1833 memset(&vic
, 0, sizeof vic
);
1834 vic
.vic_indat
= shoup
->s_dat
;
1835 vic
.vic_inlen
= shoup
->s_len
;
1836 if(!n_visual_info(&vic
,
1837 n_VISUAL_INFO_SKIP_ERRORS
| n_VISUAL_INFO_WIDTH_QUERY
))
1838 vic
.vic_vi_width
= shoup
->s_len
;
1840 /* Put on screen. Indent follow lines of same sort slot */
1843 scrwid
< lnlen
|| scrwid
- lnlen
<= vic
.vic_vi_width
+ 2){
1850 }else if(lnlen
> 0){
1855 fputs(n_string_cp(shoup
), fp
);
1856 lnlen
+= vic
.vic_vi_width
;
1858 /* Support the known file name tagging TODO optional */
1859 if(!lstat(fullpath
, &sb
)){
1862 if(S_ISDIR(sb
.st_mode
))
1864 else if(S_ISLNK(sb
.st_mode
))
1867 else if(S_ISFIFO(sb
.st_mode
))
1871 else if(S_ISSOCK(sb
.st_mode
))
1875 else if(S_ISCHR(sb
.st_mode
))
1879 else if(S_ISBLK(sb
.st_mode
))
1892 page_or_print(fp
, lncnt
);
1896 n_string_gut(shoup
);
1898 /* A common prefix of 0 means we cannot provide the user any auto
1899 * completed characters */
1903 /* Otherwise we can, so extend the visual line content by the common
1904 * prefix (in a reversible way) */
1905 (exp
.s
= UNCONST(lococp
))[locolen
] = '\0';
1907 exp
.l
= (locolen
+= prefixlen
);
1909 /* XXX Indicate that there is multiple choice */
1910 /* XXX f |= a_TTY_VF_BELL; -> *line-editor-audible-completion*? or so */
1916 /* If we've provided a default content, but failed to expand, there is
1917 * nothing we can "revert to": drop that default again */
1919 tlp
->tl_savec
.s
= NULL
;
1920 tlp
->tl_savec
.l
= 0;
1927 # ifdef HAVE_HISTORY
1929 a_tty__khist_shared(struct a_tty_line
*tlp
, struct a_tty_hist
*thp
){
1933 if(LIKELY((tlp
->tl_hist
= thp
) != NULL
)){
1934 tlp
->tl_defc
.s
= savestrbuf(thp
->th_dat
, thp
->th_len
);
1935 rv
= tlp
->tl_defc
.l
= thp
->th_len
;
1936 f
= (tlp
->tl_count
> 0) ? a_TTY_VF_MOD_DIRTY
: a_TTY_VF_NONE
;
1937 tlp
->tl_count
= tlp
->tl_cursor
= 0;
1943 tlp
->tl_vi_flags
|= f
;
1949 a_tty_khist(struct a_tty_line
*tlp
, bool_t backwd
){
1950 struct a_tty_hist
*thp
;
1954 /* If we're not in history mode yet, save line content;
1955 * also, disallow forward search, then, and, of course, bail unless we
1956 * do have any history at all */
1957 if((thp
= tlp
->tl_hist
) == NULL
){
1960 if((thp
= a_tty
.tg_hist
) == NULL
)
1962 a_tty_cell2save(tlp
);
1966 thp
= backwd
? thp
->th_older
: thp
->th_younger
;
1968 rv
= a_tty__khist_shared(tlp
, thp
);
1974 a_tty_krhist(struct a_tty_line
*tlp
){
1975 struct str orig_savec
;
1976 struct a_tty_hist
*thp
;
1982 /* We cannot complete an empty line */
1983 if(UNLIKELY(tlp
->tl_count
== 0)){
1984 /* XXX The upcoming hard reset would restore a set savec buffer,
1985 * XXX so forcefully reset that. A cleaner solution would be to
1986 * XXX reset it whenever a restore is no longer desired */
1987 tlp
->tl_savec
.s
= NULL
;
1988 tlp
->tl_savec
.l
= 0;
1992 if((thp
= tlp
->tl_hist
) == NULL
){
1993 if((thp
= a_tty
.tg_hist
) == NULL
)
1995 orig_savec
.s
= NULL
;
1996 orig_savec
.l
= 0; /* silence CC */
1997 }else if((thp
= thp
->th_older
) == NULL
)
2000 orig_savec
= tlp
->tl_savec
;
2002 if(orig_savec
.s
== NULL
)
2003 a_tty_cell2save(tlp
);
2005 for(; thp
!= NULL
; thp
= thp
->th_older
)
2006 if(is_prefix(tlp
->tl_savec
.s
, thp
->th_dat
))
2009 if(orig_savec
.s
!= NULL
)
2010 tlp
->tl_savec
= orig_savec
;
2012 rv
= a_tty__khist_shared(tlp
, thp
);
2016 # endif /* HAVE_HISTORY */
2019 a_tty_readline(struct a_tty_line
*tlp
, size_t len SMALLOC_DEBUG_ARGS
){
2020 /* We want to save code, yet we may have to incorporate a lines'
2021 * default content and / or default input to switch back to after some
2022 * history movement; let "len > 0" mean "have to display some data
2023 * buffer", and only otherwise read(2) it */
2025 char cbuf_base
[MB_LEN_MAX
* 2], *cbuf
, *cbufp
, cursor_maybe
, cursor_store
;
2031 memset(ps
, 0, sizeof ps
);
2032 tlp
->tl_vi_flags
|= a_TTY_VF_REFRESH
| a_TTY_VF_SYNC
;
2034 for(cursor_maybe
= cursor_store
= 0;;){
2035 /* Ensure we have valid pointers, and room for grow */
2036 a_tty_check_grow(tlp
, (len
== 0 ? 1 : (ui32_t
)len
)
2037 SMALLOC_DEBUG_ARGSCALL
);
2039 /* Handle visual state flags, except in buffer take-over mode */
2041 if(tlp
->tl_vi_flags
& a_TTY_VF_BELL
){
2042 tlp
->tl_vi_flags
|= a_TTY_VF_SYNC
;
2046 if(tlp
->tl_vi_flags
& a_TTY_VF_REFRESH
){
2047 /* kht may want to restore a cursor position after inserting some
2049 if(tlp
->tl_defc_cursor_byte
> 0){
2052 a_tty_khome(tlp
, FAL0
);
2054 i
= tlp
->tl_defc_cursor_byte
;
2055 tlp
->tl_defc_cursor_byte
= 0;
2056 for(j
= 0; tlp
->tl_cursor
< tlp
->tl_count
; ++j
){
2058 if((len
= tlp
->tl_line
.cells
[j
].tc_count
) > i
)
2065 if(!a_tty_vi_refresh(tlp
)){
2066 clearerr(stdout
); /* xxx I/O layer rewrite */
2067 n_err(_("Visual refresh failed! Is $TERM set correctly?\n"
2068 " Setting *line-editor-disable* to get us through!\n"));
2069 ok_bset(line_editor_disable
, TRU1
);
2075 if(tlp
->tl_vi_flags
& a_TTY_VF_SYNC
){
2076 tlp
->tl_vi_flags
&= ~a_TTY_VF_SYNC
;
2080 tlp
->tl_vi_flags
&= ~a_TTY_VF_ALL_MASK
;
2083 /* Ready for messing around.
2084 * Normal read(2)? Else buffer-takeover: speed this one up */
2089 assert(tlp
->tl_defc
.l
> 0 && tlp
->tl_defc
.s
!= NULL
);
2091 cbuf
= tlp
->tl_defc
.s
+ (tlp
->tl_defc
.l
- len
);
2095 /* Read in the next complete multibyte character */
2098 /* Let me at least once dream of iomon(itor), timer with one-shot,
2099 * enwrapped with key_event and key_sequence_event, all driven by
2101 if((rv
= read(STDIN_FILENO
, cbufp
, 1)) < 1){
2102 if(errno
== EINTR
) /* xxx #if !SA_RESTART ? */
2109 rv
= (ssize_t
)mbrtowc(&wc
, cbuf
, PTR2SIZE(cbufp
- cbuf
), &ps
[0]);
2111 /* Any error during take-over can only result in a hard reset;
2112 * Otherwise, if it's a hard error, or if too many redundant shift
2113 * sequences overflow our buffer, also perform a hard reset */
2114 if(len
!= 0 || rv
== -1 ||
2115 sizeof cbuf_base
== PTR2SIZE(cbufp
- cbuf
)){
2116 tlp
->tl_savec
.s
= tlp
->tl_defc
.s
= NULL
;
2117 tlp
->tl_savec
.l
= tlp
->tl_defc
.l
= len
= 0;
2118 tlp
->tl_defc_cursor_byte
= 0;
2119 tlp
->tl_vi_flags
|= a_TTY_VF_BELL
;
2122 /* Otherwise, due to the way we deal with the buffer, we need to
2123 * restore the mbstate_t from before this conversion */
2129 /* Buffer takeover completed? */
2130 if(len
!= 0 && (len
-= (size_t)rv
) == 0){
2131 tlp
->tl_defc
.s
= NULL
;
2137 /* Don't interpret control bytes during buffer take-over */
2138 if(cbuf
!= cbuf_base
)
2141 case 'A' ^ 0x40: /* cursor home */
2142 a_tty_khome(tlp
, TRU1
);
2144 case 'B' ^ 0x40: /* backward character */
2148 /* 'C': interrupt (CTRL-C) */
2149 case 'D' ^ 0x40: /* delete char forward if any, else EOF */
2150 if((rv
= a_tty_kdel(tlp
)) < 0)
2153 case 'E' ^ 0x40: /* end of line */
2156 case 'F' ^ 0x40: /* forward character */
2161 case 'H' ^ 0x40: /* backspace */
2165 case 'I' ^ 0x40: /* horizontal tab */
2166 if((len
= a_tty_kht(tlp
)) > 0)
2169 case 'J' ^ 0x40: /* NL (\n) */
2171 case 'G' ^ 0x40: /* full reset */
2174 case 'U' ^ 0x40: /* ^U: ^A + ^K */
2175 a_tty_khome(tlp
, FAL0
);
2177 case 'K' ^ 0x40: /* kill from cursor to end of line */
2178 a_tty_kkill(tlp
, (wc
== ('K' ^ 0x40) || tlp
->tl_count
== 0));
2179 /* (Handle full reset?) */
2180 if(wc
== ('G' ^ 0x40)) {
2181 # ifdef HAVE_HISTORY
2182 tlp
->tl_hist
= NULL
;
2184 if((len
= tlp
->tl_savec
.l
) != 0){
2185 tlp
->tl_defc
= tlp
->tl_savec
;
2186 tlp
->tl_savec
.s
= NULL
;
2187 tlp
->tl_savec
.l
= 0;
2189 len
= tlp
->tl_defc
.l
;
2192 case 'L' ^ 0x40: /* repaint line */
2193 tlp
->tl_vi_flags
|= a_TTY_VF_MOD_DIRTY
;
2196 case 'N' ^ 0x40: /* history next */
2198 # ifdef HAVE_HISTORY
2199 if(tlp
->tl_hist
== NULL
)
2201 if((len
= a_tty_khist(tlp
, FAL0
)) > 0)
2209 tlp
->tl_reenter_after_cmd
= "dp";
2211 case 'P' ^ 0x40: /* history previous */
2213 # ifdef HAVE_HISTORY
2214 if((len
= a_tty_khist(tlp
, TRU1
)) > 0)
2222 case 'R' ^ 0x40: /* reverse history search */
2223 # ifdef HAVE_HISTORY
2224 if((len
= a_tty_krhist(tlp
)) > 0)
2233 case 'V' ^ 0x40: /* insert (Unicode) character */
2234 if((wc
= a_tty_vinuni(tlp
)) > 0)
2237 case 'W' ^ 0x40: /* backward delete "word" */
2238 a_tty_kbwddelw(tlp
);
2240 case 'X' ^ 0x40: /* move cursor forward "word" */
2241 a_tty_kgow(tlp
, +1);
2243 case 'Y' ^ 0x40: /* move cursor backward "word" */
2244 a_tty_kgow(tlp
, -1);
2246 /* 'Z': suspend (CTRL-Z) */
2248 if(cursor_maybe
++ != 0)
2252 /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */
2253 if(cursor_maybe
> 0){
2254 if(++cursor_maybe
== 2){
2258 }else if(cursor_maybe
== 3){
2262 case L
'A': goto j_p
;
2263 case L
'B': goto j_n
;
2264 case L
'C': goto j_f
;
2265 case L
'D': goto j_b
;
2276 cursor_store
= ((wc
== L
'1') ? '0' :
2277 (wc
== L
'4' ? '$' : (wc
== L
'5' ? '-' : '+')));
2281 a_tty_kother(tlp
, L
'[');
2284 if(wc
== L
'~') J_xterm_noapp
:{
2285 char *cp
= salloc(3);
2288 cp
[1] = cursor_store
;
2290 tlp
->tl_reenter_after_cmd
= cp
;
2292 }else if(cursor_store
== '-' && (wc
== L
'A' || wc
== L
'B')){
2293 char const cmd
[] = "dotmove";
2294 char *cp
= salloc(sizeof(cmd
) -1 + 1 +1);
2296 memcpy(cp
, cmd
, sizeof(cmd
) -1);
2297 cp
[sizeof(cmd
) -1] = (wc
!= L
'A') ? '+' : cursor_store
;
2298 cp
[sizeof(cmd
)] = '\0';
2299 tlp
->tl_reenter_after_cmd
= cp
;
2302 a_tty_kother(tlp
, L
'[');
2303 a_tty_kother(tlp
, (wchar_t)cursor_store
);
2309 a_tty_kother(tlp
, wc
);
2310 /* Don't clear the history during takeover..
2311 * ..and also avoid fflush()ing unless we've worked entire buffer */
2314 # ifdef HAVE_HISTORY
2315 if(cbuf
== cbuf_base
)
2316 tlp
->tl_hist
= NULL
;
2320 tlp
->tl_vi_flags
|= a_TTY_VF_BELL
;
2324 tlp
->tl_vi_flags
|= a_TTY_VF_SYNC
;
2327 /* We have a completed input line, convert the struct cell data to its
2328 * plain character equivalent */
2330 rv
= a_tty_cell2dat(tlp
);
2340 # ifdef HAVE_HISTORY
2345 size_t lsize
, cnt
, llen
;
2349 /* Load the history file */
2350 # ifdef HAVE_HISTORY
2352 a_tty
.tg_hist_size
= 0;
2353 a_tty
.tg_hist_size_max
= (size_t)hs
;
2361 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
2362 f
= fopen(v
, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
2365 (void)n_file_lock(fileno(f
), FLT_READ
, 0,0, 500);
2369 cnt
= (size_t)fsize(f
);
2370 while(fgetline(&lbuf
, &lsize
, &cnt
, &llen
, f
, FAL0
) != NULL
){
2371 if(llen
> 0 && lbuf
[llen
- 1] == '\n')
2372 lbuf
[--llen
] = '\0';
2373 if(llen
== 0 || lbuf
[0] == '#') /* xxx comments? noone! */
2376 bool_t isgabby
= (lbuf
[0] == '*');
2378 n_tty_addhist(lbuf
+ isgabby
, isgabby
);
2386 rele_all_sigs(); /* XXX remove jumps */
2388 # endif /* HAVE_HISTORY */
2389 pstate
|= PS_HISTORY_LOADED
;
2394 n_tty_destroy(void){
2395 # ifdef HAVE_HISTORY
2398 struct a_tty_hist
*thp
;
2404 # ifdef HAVE_HISTORY
2413 dogabby
= ok_blook(history_gabby_persist
);
2415 if((thp
= a_tty
.tg_hist
) != NULL
)
2416 for(; thp
->th_older
!= NULL
; thp
= thp
->th_older
)
2417 if((dogabby
|| !thp
->th_isgabby
) && --hs
== 0)
2420 hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
2421 f
= fopen(v
, "w"); /* TODO temporary + rename?! */
2424 (void)n_file_lock(fileno(f
), FLT_WRITE
, 0,0, 500);
2426 for(; thp
!= NULL
; thp
= thp
->th_younger
){
2427 if(dogabby
|| !thp
->th_isgabby
){
2430 fwrite(thp
->th_dat
, sizeof *thp
->th_dat
, thp
->th_len
, f
);
2436 rele_all_sigs(); /* XXX remove jumps */
2438 # endif /* HAVE_HISTORY */
2443 n_tty_signal(int sig
){
2444 sigset_t nset
, oset
;
2445 NYD_X
; /* Signal handler */
2449 /* We don't deal with SIGWINCH, yet get called from main.c */
2452 a_tty_term_mode(FAL0
);
2453 n_TERMCAP_SUSPEND(TRU1
);
2457 sigaddset(&nset
, sig
);
2458 sigprocmask(SIG_UNBLOCK
, &nset
, &oset
);
2460 /* When we come here we'll continue editing, so reestablish */
2461 sigprocmask(SIG_BLOCK
, &oset
, (sigset_t
*)NULL
);
2464 n_TERMCAP_RESUME(TRU1
);
2465 a_tty_term_mode(TRU1
);
2471 (n_tty_readline
)(char const *prompt
, char **linebuf
, size_t *linesize
, size_t n
2472 SMALLOC_DEBUG_ARGS
){
2473 struct a_tty_line tl
;
2477 ui32_t plen
, pwidth
;
2482 n_colour_env_create(n_COLOUR_CTX_MLE
, FAL0
);
2485 /* Classify prompt */
2489 size_t i
= strlen(prompt
);
2491 if(i
== 0 || i
>= UI32_MAX
)
2494 /* TODO *prompt* is in multibyte and not in a_tty_cell, therefore
2495 * TODO we cannot handle it in parts, it's all or nothing.
2496 * TODO Later (S-CText, SysV signals) the prompt should be some global
2497 * TODO carrier thing, fully evaluated and passed around as UI-enabled
2498 * TODO string, then we can print it character by character */
2499 struct n_visual_info_ctx vic
;
2501 memset(&vic
, 0, sizeof vic
);
2502 vic
.vic_indat
= prompt
;
2504 if(n_visual_info(&vic
, n_VISUAL_INFO_WIDTH_QUERY
)){
2505 pwidth
= (ui32_t
)vic
.vic_vi_width
;
2508 n_err(_("Character set error in evaluation of prompt\n"));
2516 struct n_colour_pen
*ccp
;
2517 struct str
const *sp
;
2519 if(prompt
!= NULL
&&
2520 (ccp
= n_colour_pen_create(n_COLOUR_ID_MLE_PROMPT
, NULL
)) != NULL
&&
2521 (sp
= n_colour_pen_to_str(ccp
)) != NULL
){
2522 char const *ccol
= sp
->s
;
2524 if((sp
= n_colour_reset_to_str()) != NULL
){
2525 size_t l1
= strlen(ccol
), l2
= strlen(sp
->s
);
2526 ui32_t nplen
= (ui32_t
)(l1
+ plen
+ l2
);
2527 char *nprompt
= salloc(nplen
+1);
2529 memcpy(nprompt
, ccol
, l1
);
2530 memcpy(&nprompt
[l1
], prompt
, plen
);
2531 memcpy(&nprompt
[l1
+= plen
], sp
->s
, ++l2
);
2538 /* .tl_pos_buf is a hack */
2539 posbuf
= pos
= NULL
;
2540 if((ccp
= n_colour_pen_create(n_COLOUR_ID_MLE_POSITION
, NULL
)) != NULL
&&
2541 (sp
= n_colour_pen_to_str(ccp
)) != NULL
){
2542 char const *ccol
= sp
->s
;
2544 if((sp
= n_colour_reset_to_str()) != NULL
){
2545 size_t l1
= strlen(ccol
), l2
= strlen(sp
->s
);
2547 posbuf
= salloc(l1
+ 4 + l2
+1);
2548 memcpy(posbuf
, ccol
, l1
);
2550 memcpy(&pos
[4], sp
->s
, ++l2
);
2554 posbuf
= pos
= salloc(4 +1);
2558 # endif /* HAVE_COLOUR */
2561 memset(&tl
, 0, sizeof tl
);
2563 if((tl
.tl_prompt
= prompt
) != NULL
){ /* XXX not re-evaluated */
2564 tl
.tl_prompt_length
= plen
;
2565 tl
.tl_prompt_width
= pwidth
;
2568 tl
.tl_pos_buf
= posbuf
;
2572 tl
.tl_line
.cbuf
= *linebuf
;
2574 tl
.tl_defc
.s
= savestrbuf(*linebuf
, n
);
2577 tl
.tl_x_buf
= linebuf
;
2578 tl
.tl_x_bufsize
= linesize
;
2581 a_tty_term_mode(TRU1
);
2582 nn
= a_tty_readline(&tl
, n SMALLOC_DEBUG_ARGSCALL
);
2583 a_tty_term_mode(FAL0
);
2586 if(tl
.tl_reenter_after_cmd
!= NULL
){
2588 n_source_command(tl
.tl_reenter_after_cmd
);
2593 n_colour_env_gut(stdout
);
2600 n_tty_addhist(char const *s
, bool_t isgabby
){
2601 # ifdef HAVE_HISTORY
2602 /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
2604 struct a_tty_hist
*thp
, *othp
, *ythp
;
2610 # ifdef HAVE_HISTORY
2611 if(isgabby
&& !ok_blook(history_gabby
))
2614 if(a_tty
.tg_hist_size_max
== 0)
2616 a_TTY_CHECK_ADDHIST(s
, goto j_leave
);
2618 l
= (ui32_t
)strlen(s
);
2620 /* Eliminating duplicates is expensive, but simply inacceptable so
2621 * during the load of a potentially large history file! */
2622 if(pstate
& PS_HISTORY_LOADED
)
2623 for(thp
= a_tty
.tg_hist
; thp
!= NULL
; thp
= thp
->th_older
)
2624 if(thp
->th_len
== l
&& !strcmp(thp
->th_dat
, s
)){
2625 hold_all_sigs(); /* TODO */
2627 thp
->th_isgabby
= !!isgabby
;
2628 othp
= thp
->th_older
;
2629 ythp
= thp
->th_younger
;
2631 othp
->th_younger
= ythp
;
2633 a_tty
.tg_hist_tail
= ythp
;
2635 ythp
->th_older
= othp
;
2637 a_tty
.tg_hist
= othp
;
2642 ++a_tty
.tg_hist_size
;
2643 if((pstate
& PS_HISTORY_LOADED
) &&
2644 a_tty
.tg_hist_size
> a_tty
.tg_hist_size_max
){
2645 --a_tty
.tg_hist_size
;
2646 if((thp
= a_tty
.tg_hist_tail
) != NULL
){
2647 if((a_tty
.tg_hist_tail
= thp
->th_younger
) == NULL
)
2648 a_tty
.tg_hist
= NULL
;
2650 a_tty
.tg_hist_tail
->th_older
= NULL
;
2655 thp
= smalloc((sizeof(struct a_tty_hist
) -
2656 VFIELD_SIZEOF(struct a_tty_hist
, th_dat
)) + l
+1);
2657 thp
->th_isgabby
= !!isgabby
;
2659 memcpy(thp
->th_dat
, s
, l
+1);
2661 if((thp
->th_older
= a_tty
.tg_hist
) != NULL
)
2662 a_tty
.tg_hist
->th_younger
= thp
;
2664 a_tty
.tg_hist_tail
= thp
;
2665 thp
->th_younger
= NULL
;
2666 a_tty
.tg_hist
= thp
;
2674 # ifdef HAVE_HISTORY
2682 struct a_tty_hist
*thp
;
2684 if(a_tty
.tg_hist
== NULL
)
2687 if((fp
= Ftmp(NULL
, "hist", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
){
2688 n_perr(_("tmpfile"), 0);
2693 i
= a_tty
.tg_hist_size
;
2695 for(thp
= a_tty
.tg_hist
; thp
!= NULL
;
2696 --i
, b
+= thp
->th_len
, thp
= thp
->th_older
)
2698 "%c%4" PRIuZ
". %-50.50s (%4" PRIuZ
"+%2" PRIu32
" B)\n",
2699 (thp
->th_isgabby
? '*' : ' '), i
, thp
->th_dat
, b
, thp
->th_len
);
2701 page_or_print(fp
, i
);
2707 struct a_tty_hist
*thp
;
2709 while((thp
= a_tty
.tg_hist
) != NULL
){
2710 a_tty
.tg_hist
= thp
->th_older
;
2713 a_tty
.tg_hist_tail
= NULL
;
2714 a_tty
.tg_hist_size
= 0;
2719 struct a_tty_hist
*thp
;
2721 if(UICMP(z
, entry
, <=, a_tty
.tg_hist_size
)){
2722 entry
= (long)a_tty
.tg_hist_size
- entry
;
2723 for(thp
= a_tty
.tg_hist
;; thp
= thp
->th_older
)
2726 else if(entry
-- != 0)
2729 v
= temporary_arg_v_store
= thp
->th_dat
;
2737 # endif /* HAVE_HISTORY */
2738 #endif /* HAVE_MLE */
2741 * The really-nothing-at-all implementation
2743 #if !defined HAVE_READLINE && !defined HAVE_MLE
2752 n_tty_destroy(void){
2758 n_tty_signal(int sig
){
2759 NYD_X
; /* Signal handler */
2762 # ifdef HAVE_TERMCAP
2765 sigset_t nset
, oset
;
2767 n_TERMCAP_SUSPEND(TRU1
);
2771 sigaddset(&nset
, sig
);
2772 sigprocmask(SIG_UNBLOCK
, &nset
, &oset
);
2774 /* When we come here we'll continue editing, so reestablish */
2775 sigprocmask(SIG_BLOCK
, &oset
, (sigset_t
*)NULL
);
2778 n_TERMCAP_RESUME(TRU1
);
2782 # endif /* HAVE_TERMCAP */
2786 (n_tty_readline
)(char const *prompt
, char **linebuf
, size_t *linesize
, size_t n
2787 SMALLOC_DEBUG_ARGS
){
2793 fputs(prompt
, stdout
);
2796 # ifdef HAVE_TERMCAP
2799 rv
= (readline_restart
)(stdin
, linebuf
, linesize
,n SMALLOC_DEBUG_ARGSCALL
);
2800 # ifdef HAVE_TERMCAP
2808 n_tty_addhist(char const *s
, bool_t isgabby
){
2814 #endif /* nothing at all */
2816 #undef a_TTY_SIGNALS