vendor/dialog: upgrade from 1.2-20150920 to 1.3-20200327
[dragonfly.git] / contrib / dialog / inputstr.c
1 /*
2  *  $Id: inputstr.c,v 1.90 2019/07/24 23:42:20 tom Exp $
3  *
4  *  inputstr.c -- functions for input/display of a string
5  *
6  *  Copyright 2000-2018,2019    Thomas E. Dickey
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License, version 2.1
10  *  as published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this program; if not, write to
19  *      Free Software Foundation, Inc.
20  *      51 Franklin St., Fifth Floor
21  *      Boston, MA 02110, USA.
22  */
23
24 #include <dialog.h>
25 #include <dlg_keys.h>
26
27 #include <errno.h>
28
29 #ifdef HAVE_SETLOCALE
30 #include <locale.h>
31 #endif
32
33 #if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH)
34 #include <search.h>
35 #else
36 #undef HAVE_TSEARCH
37 #endif
38
39 #ifdef NEED_WCHAR_H
40 #include <wchar.h>
41 #endif
42
43 #if defined(USE_WIDE_CURSES)
44 #define USE_CACHING 1
45 #elif defined(HAVE_XDIALOG)
46 #define USE_CACHING 1           /* editbox really needs caching! */
47 #else
48 #define USE_CACHING 0
49 #endif
50
51 typedef struct _cache {
52     struct _cache *next;
53 #if USE_CACHING
54     int cache_num;              /* tells what type of data is in list[] */
55     const char *string_at;      /* unique: associate caches by char* */
56 #endif
57     size_t s_len;               /* strlen(string) - we add 1 for EOS */
58     size_t i_len;               /* length(list) - we add 1 for EOS */
59     char *string;               /* a copy of the last-processed string */
60     int *list;                  /* indices into the string */
61 } CACHE;
62
63 #if USE_CACHING
64 #define SAME_CACHE(c,s,l) (c->string != 0 && memcmp(c->string,s,l) == 0)
65
66 static CACHE *cache_list;
67
68 typedef enum {
69     cInxCols
70     ,cCntWideBytes
71     ,cCntWideChars
72     ,cInxWideChars
73     ,cMAX
74 } CACHE_USED;
75
76 #ifdef HAVE_TSEARCH
77 static void *sorted_cache;
78 #endif
79
80 #ifdef USE_WIDE_CURSES
81 static int
82 have_locale(void)
83 {
84     static int result = -1;
85     if (result < 0) {
86         char *test = setlocale(LC_ALL, 0);
87         if (test == 0 || *test == 0) {
88             result = FALSE;
89         } else if (strcmp(test, "C") && strcmp(test, "POSIX")) {
90             result = TRUE;
91         } else {
92             result = FALSE;
93         }
94     }
95     return result;
96 }
97 #endif
98
99 #ifdef HAVE_TSEARCH
100
101 #if 0
102 static void
103 show_tsearch(const void *nodep, const VISIT which, const int depth)
104 {
105     const CACHE *p = *(CACHE * const *) nodep;
106     (void) depth;
107     if (which == postorder || which == leaf) {
108         DLG_TRACE(("# cache %p %p:%s\n", p, p->string, p->string));
109     }
110 }
111
112 static void
113 trace_cache(const char *fn, int ln)
114 {
115     DLG_TRACE(("# trace_cache %s@%d\n", fn, ln));
116     twalk(sorted_cache, show_tsearch);
117 }
118
119 #else
120 #define trace_cache(fn, ln)     /* nothing */
121 #endif
122
123 #define CMP(a,b) (((a) > (b)) ? 1 : (((a) < (b)) ? -1 : 0))
124
125 static int
126 compare_cache(const void *a, const void *b)
127 {
128     const CACHE *p = (const CACHE *) a;
129     const CACHE *q = (const CACHE *) b;
130     int result = CMP(p->cache_num, q->cache_num);
131     if (result == 0)
132         result = CMP(p->string_at, q->string_at);
133     return result;
134 }
135 #endif
136
137 static CACHE *
138 find_cache(int cache_num, const char *string)
139 {
140     CACHE *p;
141
142 #ifdef HAVE_TSEARCH
143     void *pp;
144     CACHE find;
145
146     memset(&find, 0, sizeof(find));
147     find.cache_num = cache_num;
148     find.string_at = string;
149
150     if ((pp = tfind(&find, &sorted_cache, compare_cache)) != 0) {
151         p = *(CACHE **) pp;
152     } else {
153         p = 0;
154     }
155 #else
156     for (p = cache_list; p != 0; p = p->next) {
157         if (p->string_at == string) {
158             break;
159         }
160     }
161 #endif
162     return p;
163 }
164
165 static CACHE *
166 make_cache(int cache_num, const char *string)
167 {
168     CACHE *p;
169
170     p = dlg_calloc(CACHE, 1);
171     assert_ptr(p, "load_cache");
172     p->next = cache_list;
173     cache_list = p;
174
175     p->cache_num = cache_num;
176     p->string_at = string;
177
178 #ifdef HAVE_TSEARCH
179     (void) tsearch(p, &sorted_cache, compare_cache);
180 #endif
181     return p;
182 }
183
184 static CACHE *
185 load_cache(int cache_num, const char *string)
186 {
187     CACHE *p;
188
189     if ((p = find_cache(cache_num, string)) == 0) {
190         p = make_cache(cache_num, string);
191     }
192     return p;
193 }
194 #else
195 static CACHE my_cache;
196 #define SAME_CACHE(c,s,l) (c->string != 0)
197 #define load_cache(cache, string) &my_cache
198 #endif /* USE_CACHING */
199
200 /*
201  * If the given string has not changed, we do not need to update the index.
202  * If we need to update the index, allocate enough memory for it.
203  */
204 static bool
205 same_cache2(CACHE * cache, const char *string, unsigned i_len)
206 {
207     size_t s_len = strlen(string);
208     bool result = TRUE;
209
210     if (cache->s_len == 0
211         || cache->s_len < s_len
212         || cache->list == 0
213         || !SAME_CACHE(cache, string, (size_t) s_len)) {
214         unsigned need = (i_len + 1);
215
216         if (cache->list == 0) {
217             cache->list = dlg_malloc(int, need);
218         } else if (cache->i_len < i_len) {
219             cache->list = dlg_realloc(int, need, cache->list);
220         }
221         assert_ptr(cache->list, "load_cache");
222         cache->i_len = i_len;
223
224         if (cache->s_len >= s_len && cache->string != 0) {
225             strcpy(cache->string, string);
226         } else {
227             if (cache->string != 0)
228                 free(cache->string);
229             cache->string = dlg_strclone(string);
230         }
231         cache->s_len = s_len;
232
233         result = FALSE;
234     }
235     return result;
236 }
237
238 #ifdef USE_WIDE_CURSES
239 /*
240  * Like same_cache2(), but we are only concerned about caching a copy of the
241  * string and its associated length.
242  */
243 static bool
244 same_cache1(CACHE * cache, const char *string, size_t i_len)
245 {
246     size_t s_len = strlen(string);
247     bool result = TRUE;
248
249     if (cache->s_len != s_len
250         || !SAME_CACHE(cache, string, (size_t) s_len)) {
251
252         if (cache->s_len >= s_len && cache->string != 0) {
253             strcpy(cache->string, string);
254         } else {
255             if (cache->string != 0)
256                 free(cache->string);
257             cache->string = dlg_strclone(string);
258         }
259         cache->s_len = s_len;
260         cache->i_len = i_len;
261
262         result = FALSE;
263     }
264     return result;
265 }
266 #endif /* USE_CACHING */
267
268 /*
269  * Counts the number of bytes that make up complete wide-characters, up to byte
270  * 'len'.  If there is no locale set, simply return the original length.
271  */
272 #ifdef USE_WIDE_CURSES
273 static int
274 dlg_count_wcbytes(const char *string, size_t len)
275 {
276     int result;
277
278     if (have_locale()) {
279         CACHE *cache = load_cache(cCntWideBytes, string);
280         if (!same_cache1(cache, string, len)) {
281             while (len != 0) {
282                 size_t code = 0;
283                 const char *src = cache->string;
284                 mbstate_t state;
285                 char save = cache->string[len];
286
287                 cache->string[len] = '\0';
288                 memset(&state, 0, sizeof(state));
289                 code = mbsrtowcs((wchar_t *) 0, &src, len, &state);
290                 cache->string[len] = save;
291                 if ((int) code >= 0) {
292                     break;
293                 }
294                 --len;
295             }
296             cache->i_len = len;
297         }
298         result = (int) cache->i_len;
299     } else {
300         result = (int) len;
301     }
302     return result;
303 }
304 #endif /* USE_WIDE_CURSES */
305
306 /*
307  * Counts the number of wide-characters in the string.
308  */
309 int
310 dlg_count_wchars(const char *string)
311 {
312     int result;
313 #ifdef USE_WIDE_CURSES
314
315     if (have_locale()) {
316         size_t len = strlen(string);
317         CACHE *cache = load_cache(cCntWideChars, string);
318
319         if (!same_cache1(cache, string, len)) {
320             const char *src = cache->string;
321             mbstate_t state;
322             int part = dlg_count_wcbytes(cache->string, len);
323             char save = cache->string[part];
324             wchar_t *temp = dlg_calloc(wchar_t, len + 1);
325
326             if (temp != 0) {
327                 size_t code;
328
329                 cache->string[part] = '\0';
330                 memset(&state, 0, sizeof(state));
331                 code = mbsrtowcs(temp, &src, (size_t) part, &state);
332                 cache->i_len = ((int) code >= 0) ? wcslen(temp) : 0;
333                 cache->string[part] = save;
334                 free(temp);
335             } else {
336                 cache->i_len = 0;
337             }
338         }
339         result = (int) cache->i_len;
340     } else
341 #endif /* USE_WIDE_CURSES */
342     {
343         result = (int) strlen(string);
344     }
345     return result;
346 }
347
348 /*
349  * Build an index of the wide-characters in the string, so we can easily tell
350  * which byte-offset begins a given wide-character.
351  */
352 const int *
353 dlg_index_wchars(const char *string)
354 {
355     unsigned len = (unsigned) dlg_count_wchars(string);
356     CACHE *cache = load_cache(cInxWideChars, string);
357
358     if (!same_cache2(cache, string, len)) {
359         const char *current = string;
360         unsigned inx;
361
362         cache->list[0] = 0;
363         for (inx = 1; inx <= len; ++inx) {
364 #ifdef USE_WIDE_CURSES
365             if (have_locale()) {
366                 mbstate_t state;
367                 int width;
368                 memset(&state, 0, sizeof(state));
369                 width = (int) mbrlen(current, strlen(current), &state);
370                 if (width <= 0)
371                     width = 1;  /* FIXME: what if we have a control-char? */
372                 current += width;
373                 cache->list[inx] = cache->list[inx - 1] + width;
374             } else
375 #endif /* USE_WIDE_CURSES */
376             {
377                 (void) current;
378                 cache->list[inx] = (int) inx;
379             }
380         }
381     }
382     return cache->list;
383 }
384
385 /*
386  * Given the character-offset to find in the list, return the corresponding
387  * array index.
388  */
389 int
390 dlg_find_index(const int *list, int limit, int to_find)
391 {
392     int result;
393     for (result = 0; result <= limit; ++result) {
394         if (to_find == list[result]
395             || result == limit
396             || ((result < limit) && (to_find < list[result + 1]))) {
397             break;
398         }
399     }
400     return result;
401 }
402
403 /*
404  * Build a list of the display-columns for the given string's characters.
405  */
406 const int *
407 dlg_index_columns(const char *string)
408 {
409     unsigned len = (unsigned) dlg_count_wchars(string);
410     CACHE *cache = load_cache(cInxCols, string);
411
412     if (!same_cache2(cache, string, len)) {
413         unsigned inx;
414
415         cache->list[0] = 0;
416 #ifdef USE_WIDE_CURSES
417         if (have_locale()) {
418             size_t num_bytes = strlen(string);
419             const int *inx_wchars = dlg_index_wchars(string);
420             mbstate_t state;
421
422             for (inx = 0; inx < len; ++inx) {
423                 int result;
424
425                 if (string[inx_wchars[inx]] == TAB) {
426                     result = ((cache->list[inx] | 7) + 1) - cache->list[inx];
427                 } else {
428                     wchar_t temp[2];
429                     size_t check;
430
431                     memset(&state, 0, sizeof(state));
432                     memset(temp, 0, sizeof(temp));
433                     check = mbrtowc(temp,
434                                     string + inx_wchars[inx],
435                                     num_bytes - (size_t) inx_wchars[inx],
436                                     &state);
437                     if ((int) check <= 0) {
438                         result = 1;
439                     } else {
440                         result = wcwidth(temp[0]);
441                     }
442                     if (result < 0) {
443                         const wchar_t *printable;
444                         cchar_t temp2, *temp2p = &temp2;
445                         setcchar(temp2p, temp, 0, 0, 0);
446                         printable = wunctrl(temp2p);
447                         result = printable ? (int) wcslen(printable) : 1;
448                     }
449                 }
450                 cache->list[inx + 1] = result;
451                 if (inx != 0)
452                     cache->list[inx + 1] += cache->list[inx];
453             }
454         } else
455 #endif /* USE_WIDE_CURSES */
456         {
457             for (inx = 0; inx < len; ++inx) {
458                 chtype ch = UCH(string[inx]);
459
460                 if (ch == TAB)
461                     cache->list[inx + 1] =
462                         ((cache->list[inx] | 7) + 1) - cache->list[inx];
463                 else if (isprint(UCH(ch)))
464                     cache->list[inx + 1] = 1;
465                 else {
466                     const char *printable;
467                     printable = unctrl(ch);
468                     cache->list[inx + 1] = (printable
469                                             ? (int) strlen(printable)
470                                             : 1);
471                 }
472                 if (inx != 0)
473                     cache->list[inx + 1] += cache->list[inx];
474             }
475         }
476     }
477     return cache->list;
478 }
479
480 /*
481  * Returns the number of columns used for a string.  That happens to be the
482  * end-value of the cols[] array.
483  */
484 int
485 dlg_count_columns(const char *string)
486 {
487     int result = 0;
488     int limit = dlg_count_wchars(string);
489     if (limit > 0) {
490         const int *cols = dlg_index_columns(string);
491         result = cols[limit];
492     } else {
493         result = (int) strlen(string);
494     }
495     dlg_finish_string(string);
496     return result;
497 }
498
499 /*
500  * Given a column limit, count the number of wide characters that can fit
501  * into that limit.  The offset is used to skip over a leading character
502  * that was already written.
503  */
504 int
505 dlg_limit_columns(const char *string, int limit, int offset)
506 {
507     const int *cols = dlg_index_columns(string);
508     int result = dlg_count_wchars(string);
509
510     while (result > 0 && (cols[result] - cols[offset]) > limit)
511         --result;
512     return result;
513 }
514
515 /*
516  * Updates the string and character-offset, given various editing characters
517  * or literal characters which are inserted at the character-offset.
518  */
519 bool
520 dlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force)
521 {
522     int i;
523     int len = (int) strlen(string);
524     int limit = dlg_count_wchars(string);
525     const int *indx = dlg_index_wchars(string);
526     int offset = dlg_find_index(indx, limit, *chr_offset);
527     int max_len = dlg_max_input(MAX_LEN);
528     bool edit = TRUE;
529
530     /* transform editing characters into equivalent function-keys */
531     if (!fkey) {
532         fkey = TRUE;            /* assume we transform */
533         switch (key) {
534         case 0:
535             break;
536         case ESC:
537         case TAB:
538             fkey = FALSE;       /* this is used for navigation */
539             break;
540         default:
541             fkey = FALSE;       /* ...no, we did not transform */
542             break;
543         }
544     }
545
546     if (fkey) {
547         switch (key) {
548         case 0:         /* special case for loop entry */
549             edit = force;
550             break;
551         case DLGK_GRID_LEFT:
552             if (*chr_offset && offset > 0)
553                 *chr_offset = indx[offset - 1];
554             break;
555         case DLGK_GRID_RIGHT:
556             if (offset < limit)
557                 *chr_offset = indx[offset + 1];
558             break;
559         case DLGK_BEGIN:
560             if (*chr_offset)
561                 *chr_offset = 0;
562             break;
563         case DLGK_FINAL:
564             if (offset < limit)
565                 *chr_offset = indx[limit];
566             break;
567         case DLGK_DELETE_LEFT:
568             if (offset) {
569                 int gap = indx[offset] - indx[offset - 1];
570                 *chr_offset = indx[offset - 1];
571                 if (gap > 0) {
572                     for (i = *chr_offset;
573                          (string[i] = string[i + gap]) != '\0';
574                          i++) {
575                         ;
576                     }
577                 }
578             }
579             break;
580         case DLGK_DELETE_RIGHT:
581             if (limit) {
582                 if (--limit == 0) {
583                     string[*chr_offset = 0] = '\0';
584                 } else {
585                     int gap = ((offset <= limit)
586                                ? (indx[offset + 1] - indx[offset])
587                                : 0);
588                     if (gap > 0) {
589                         for (i = indx[offset];
590                              (string[i] = string[i + gap]) != '\0';
591                              i++) {
592                             ;
593                         }
594                     } else if (offset > 0) {
595                         string[indx[offset - 1]] = '\0';
596                     }
597                     if (*chr_offset > indx[limit])
598                         *chr_offset = indx[limit];
599                 }
600             }
601             break;
602         case DLGK_DELETE_ALL:
603             string[*chr_offset = 0] = '\0';
604             break;
605         case DLGK_ENTER:
606             edit = 0;
607             break;
608 #ifdef KEY_RESIZE
609         case KEY_RESIZE:
610             edit = 0;
611             break;
612 #endif
613         case DLGK_GRID_UP:
614         case DLGK_GRID_DOWN:
615         case DLGK_FIELD_NEXT:
616         case DLGK_FIELD_PREV:
617             edit = 0;
618             break;
619         case ERR:
620             edit = 0;
621             break;
622         default:
623             beep();
624             break;
625         }
626     } else {
627         if (key == ESC || key == ERR) {
628             edit = 0;
629         } else {
630             if (len < max_len) {
631                 for (i = ++len; i > *chr_offset; i--)
632                     string[i] = string[i - 1];
633                 string[*chr_offset] = (char) key;
634                 *chr_offset += 1;
635             } else {
636                 (void) beep();
637             }
638         }
639     }
640     return edit;
641 }
642
643 static void
644 compute_edit_offset(const char *string,
645                     int chr_offset,
646                     int x_last,
647                     int *p_dpy_column,
648                     int *p_scroll_amt)
649 {
650     const int *cols = dlg_index_columns(string);
651     const int *indx = dlg_index_wchars(string);
652     int limit = dlg_count_wchars(string);
653     int offset = dlg_find_index(indx, limit, chr_offset);
654     int offset2;
655     int dpy_column;
656     int n;
657
658     for (n = offset2 = 0; n <= offset; ++n) {
659         if ((cols[offset] - cols[n]) < x_last
660             && (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) {
661             offset2 = n;
662             break;
663         }
664     }
665
666     dpy_column = cols[offset] - cols[offset2];
667
668     if (p_dpy_column != 0)
669         *p_dpy_column = dpy_column;
670     if (p_scroll_amt != 0)
671         *p_scroll_amt = offset2;
672 }
673
674 /*
675  * Given the character-offset in the string, returns the display-offset where
676  * we will position the cursor.
677  */
678 int
679 dlg_edit_offset(char *string, int chr_offset, int x_last)
680 {
681     int result;
682
683     compute_edit_offset(string, chr_offset, x_last, &result, 0);
684
685     return result;
686 }
687
688 /*
689  * Displays the string, shifted as necessary, to fit within the box and show
690  * the current character-offset.
691  */
692 void
693 dlg_show_string(WINDOW *win,
694                 const char *string,     /* string to display (may be multibyte) */
695                 int chr_offset, /* character (not bytes) offset */
696                 chtype attr,    /* window-attributes */
697                 int y_base,     /* beginning row on screen */
698                 int x_base,     /* beginning column on screen */
699                 int x_last,     /* number of columns on screen */
700                 bool hidden,    /* if true, do not echo */
701                 bool force)     /* if true, force repaint */
702 {
703     x_last = MIN(x_last + x_base, getmaxx(win)) - x_base;
704
705     if (hidden && !dialog_vars.insecure) {
706         if (force) {
707             (void) wmove(win, y_base, x_base);
708             wrefresh(win);
709         }
710     } else {
711         const int *cols = dlg_index_columns(string);
712         const int *indx = dlg_index_wchars(string);
713         int limit = dlg_count_wchars(string);
714
715         int i, j, k;
716         int input_x;
717         int scrollamt;
718
719         compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt);
720
721         dlg_attrset(win, attr);
722         (void) wmove(win, y_base, x_base);
723         for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) {
724             int check = cols[i + 1] - cols[scrollamt];
725             if (check <= x_last) {
726                 for (j = indx[i]; j < indx[i + 1]; ++j) {
727                     chtype ch = UCH(string[j]);
728                     if (hidden && dialog_vars.insecure) {
729                         waddch(win, '*');
730                     } else if (ch == TAB) {
731                         int count = cols[i + 1] - cols[i];
732                         while (--count >= 0)
733                             waddch(win, ' ');
734                     } else {
735                         waddch(win, ch);
736                     }
737                 }
738                 k = check;
739             } else {
740                 break;
741             }
742         }
743         while (k++ < x_last)
744             waddch(win, ' ');
745         (void) wmove(win, y_base, x_base + input_x);
746         wrefresh(win);
747     }
748 }
749
750 /*
751  * Discard cached data for the given string.
752  */
753 void
754 dlg_finish_string(const char *string)
755 {
756 #if USE_CACHING
757     if ((string != 0) && dialog_state.finish_string) {
758         CACHE *p = cache_list;
759         CACHE *q = 0;
760         CACHE *r;
761
762         while (p != 0) {
763             if (p->string_at == string) {
764 #ifdef HAVE_TSEARCH
765                 if (tdelete(p, &sorted_cache, compare_cache) == 0) {
766                     continue;
767                 }
768                 trace_cache(__FILE__, __LINE__);
769 #endif
770                 if (p->string != 0)
771                     free(p->string);
772                 if (p->list != 0)
773                     free(p->list);
774                 if (p == cache_list) {
775                     cache_list = p->next;
776                     r = cache_list;
777                 } else {
778                     q->next = p->next;
779                     r = q;
780                 }
781                 free(p);
782                 p = r;
783             } else {
784                 q = p;
785                 p = p->next;
786             }
787         }
788     }
789 #else
790     (void) string;
791 #endif
792 }
793
794 #ifdef NO_LEAKS
795 void
796 _dlg_inputstr_leaks(void)
797 {
798 #if USE_CACHING
799     dialog_state.finish_string = TRUE;
800     trace_cache(__FILE__, __LINE__);
801     while (cache_list != 0) {
802         dlg_finish_string(cache_list->string_at);
803     }
804 #endif /* USE_CACHING */
805 }
806 #endif /* NO_LEAKS */