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