Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/dialog/inputstr.c
39475 views
1
/*
2
* $Id: inputstr.c,v 1.91 2021/01/17 22:19:05 tom Exp $
3
*
4
* inputstr.c -- functions for input/display of a string
5
*
6
* Copyright 2000-2019,2021 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
414
cache->list[0] = 0;
415
#ifdef USE_WIDE_CURSES
416
if (have_locale()) {
417
unsigned inx;
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
unsigned inx;
458
459
for (inx = 0; inx < len; ++inx) {
460
chtype ch = UCH(string[inx]);
461
462
if (ch == TAB)
463
cache->list[inx + 1] =
464
((cache->list[inx] | 7) + 1) - cache->list[inx];
465
else if (isprint(UCH(ch)))
466
cache->list[inx + 1] = 1;
467
else {
468
const char *printable;
469
printable = unctrl(ch);
470
cache->list[inx + 1] = (printable
471
? (int) strlen(printable)
472
: 1);
473
}
474
if (inx != 0)
475
cache->list[inx + 1] += cache->list[inx];
476
}
477
}
478
}
479
return cache->list;
480
}
481
482
/*
483
* Returns the number of columns used for a string. That happens to be the
484
* end-value of the cols[] array.
485
*/
486
int
487
dlg_count_columns(const char *string)
488
{
489
int result = 0;
490
int limit = dlg_count_wchars(string);
491
if (limit > 0) {
492
const int *cols = dlg_index_columns(string);
493
result = cols[limit];
494
} else {
495
result = (int) strlen(string);
496
}
497
dlg_finish_string(string);
498
return result;
499
}
500
501
/*
502
* Given a column limit, count the number of wide characters that can fit
503
* into that limit. The offset is used to skip over a leading character
504
* that was already written.
505
*/
506
int
507
dlg_limit_columns(const char *string, int limit, int offset)
508
{
509
const int *cols = dlg_index_columns(string);
510
int result = dlg_count_wchars(string);
511
512
while (result > 0 && (cols[result] - cols[offset]) > limit)
513
--result;
514
return result;
515
}
516
517
/*
518
* Updates the string and character-offset, given various editing characters
519
* or literal characters which are inserted at the character-offset.
520
*/
521
bool
522
dlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force)
523
{
524
int i;
525
int len = (int) strlen(string);
526
int limit = dlg_count_wchars(string);
527
const int *indx = dlg_index_wchars(string);
528
int offset = dlg_find_index(indx, limit, *chr_offset);
529
int max_len = dlg_max_input(MAX_LEN);
530
bool edit = TRUE;
531
532
/* transform editing characters into equivalent function-keys */
533
if (!fkey) {
534
fkey = TRUE; /* assume we transform */
535
switch (key) {
536
case 0:
537
break;
538
case ESC:
539
case TAB:
540
fkey = FALSE; /* this is used for navigation */
541
break;
542
default:
543
fkey = FALSE; /* ...no, we did not transform */
544
break;
545
}
546
}
547
548
if (fkey) {
549
switch (key) {
550
case 0: /* special case for loop entry */
551
edit = force;
552
break;
553
case DLGK_GRID_LEFT:
554
if (*chr_offset && offset > 0)
555
*chr_offset = indx[offset - 1];
556
break;
557
case DLGK_GRID_RIGHT:
558
if (offset < limit)
559
*chr_offset = indx[offset + 1];
560
break;
561
case DLGK_BEGIN:
562
if (*chr_offset)
563
*chr_offset = 0;
564
break;
565
case DLGK_FINAL:
566
if (offset < limit)
567
*chr_offset = indx[limit];
568
break;
569
case DLGK_DELETE_LEFT:
570
if (offset) {
571
int gap = indx[offset] - indx[offset - 1];
572
*chr_offset = indx[offset - 1];
573
if (gap > 0) {
574
for (i = *chr_offset;
575
(string[i] = string[i + gap]) != '\0';
576
i++) {
577
;
578
}
579
}
580
}
581
break;
582
case DLGK_DELETE_RIGHT:
583
if (limit) {
584
if (--limit == 0) {
585
string[*chr_offset = 0] = '\0';
586
} else {
587
int gap = ((offset <= limit)
588
? (indx[offset + 1] - indx[offset])
589
: 0);
590
if (gap > 0) {
591
for (i = indx[offset];
592
(string[i] = string[i + gap]) != '\0';
593
i++) {
594
;
595
}
596
} else if (offset > 0) {
597
string[indx[offset - 1]] = '\0';
598
}
599
if (*chr_offset > indx[limit])
600
*chr_offset = indx[limit];
601
}
602
}
603
break;
604
case DLGK_DELETE_ALL:
605
string[*chr_offset = 0] = '\0';
606
break;
607
case DLGK_ENTER:
608
edit = 0;
609
break;
610
#ifdef KEY_RESIZE
611
case KEY_RESIZE:
612
edit = 0;
613
break;
614
#endif
615
case DLGK_GRID_UP:
616
case DLGK_GRID_DOWN:
617
case DLGK_FIELD_NEXT:
618
case DLGK_FIELD_PREV:
619
edit = 0;
620
break;
621
case ERR:
622
edit = 0;
623
break;
624
default:
625
beep();
626
break;
627
}
628
} else {
629
if (key == ESC || key == ERR) {
630
edit = 0;
631
} else {
632
if (len < max_len) {
633
for (i = ++len; i > *chr_offset; i--)
634
string[i] = string[i - 1];
635
string[*chr_offset] = (char) key;
636
*chr_offset += 1;
637
} else {
638
(void) beep();
639
}
640
}
641
}
642
return edit;
643
}
644
645
static void
646
compute_edit_offset(const char *string,
647
int chr_offset,
648
int x_last,
649
int *p_dpy_column,
650
int *p_scroll_amt)
651
{
652
const int *cols = dlg_index_columns(string);
653
const int *indx = dlg_index_wchars(string);
654
int limit = dlg_count_wchars(string);
655
int offset = dlg_find_index(indx, limit, chr_offset);
656
int offset2;
657
int dpy_column;
658
int n;
659
660
for (n = offset2 = 0; n <= offset; ++n) {
661
if ((cols[offset] - cols[n]) < x_last
662
&& (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) {
663
offset2 = n;
664
break;
665
}
666
}
667
668
dpy_column = cols[offset] - cols[offset2];
669
670
if (p_dpy_column != 0)
671
*p_dpy_column = dpy_column;
672
if (p_scroll_amt != 0)
673
*p_scroll_amt = offset2;
674
}
675
676
/*
677
* Given the character-offset in the string, returns the display-offset where
678
* we will position the cursor.
679
*/
680
int
681
dlg_edit_offset(char *string, int chr_offset, int x_last)
682
{
683
int result;
684
685
compute_edit_offset(string, chr_offset, x_last, &result, 0);
686
687
return result;
688
}
689
690
/*
691
* Displays the string, shifted as necessary, to fit within the box and show
692
* the current character-offset.
693
*/
694
void
695
dlg_show_string(WINDOW *win,
696
const char *string, /* string to display (may be multibyte) */
697
int chr_offset, /* character (not bytes) offset */
698
chtype attr, /* window-attributes */
699
int y_base, /* beginning row on screen */
700
int x_base, /* beginning column on screen */
701
int x_last, /* number of columns on screen */
702
bool hidden, /* if true, do not echo */
703
bool force) /* if true, force repaint */
704
{
705
x_last = MIN(x_last + x_base, getmaxx(win)) - x_base;
706
707
if (hidden && !dialog_vars.insecure) {
708
if (force) {
709
(void) wmove(win, y_base, x_base);
710
wrefresh(win);
711
}
712
} else {
713
const int *cols = dlg_index_columns(string);
714
const int *indx = dlg_index_wchars(string);
715
int limit = dlg_count_wchars(string);
716
717
int i, j, k;
718
int input_x;
719
int scrollamt;
720
721
compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt);
722
723
dlg_attrset(win, attr);
724
(void) wmove(win, y_base, x_base);
725
for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) {
726
int check = cols[i + 1] - cols[scrollamt];
727
if (check <= x_last) {
728
for (j = indx[i]; j < indx[i + 1]; ++j) {
729
chtype ch = UCH(string[j]);
730
if (hidden && dialog_vars.insecure) {
731
waddch(win, '*');
732
} else if (ch == TAB) {
733
int count = cols[i + 1] - cols[i];
734
while (--count >= 0)
735
waddch(win, ' ');
736
} else {
737
waddch(win, ch);
738
}
739
}
740
k = check;
741
} else {
742
break;
743
}
744
}
745
while (k++ < x_last)
746
waddch(win, ' ');
747
(void) wmove(win, y_base, x_base + input_x);
748
wrefresh(win);
749
}
750
}
751
752
/*
753
* Discard cached data for the given string.
754
*/
755
void
756
dlg_finish_string(const char *string)
757
{
758
#if USE_CACHING
759
if ((string != 0) && dialog_state.finish_string) {
760
CACHE *p = cache_list;
761
CACHE *q = 0;
762
CACHE *r;
763
764
while (p != 0) {
765
if (p->string_at == string) {
766
#ifdef HAVE_TSEARCH
767
if (tdelete(p, &sorted_cache, compare_cache) == 0) {
768
continue;
769
}
770
trace_cache(__FILE__, __LINE__);
771
#endif
772
if (p->string != 0)
773
free(p->string);
774
if (p->list != 0)
775
free(p->list);
776
if (p == cache_list) {
777
cache_list = p->next;
778
r = cache_list;
779
} else {
780
q->next = p->next;
781
r = q;
782
}
783
free(p);
784
p = r;
785
} else {
786
q = p;
787
p = p->next;
788
}
789
}
790
}
791
#else
792
(void) string;
793
#endif
794
}
795
796
#ifdef NO_LEAKS
797
void
798
_dlg_inputstr_leaks(void)
799
{
800
#if USE_CACHING
801
dialog_state.finish_string = TRUE;
802
trace_cache(__FILE__, __LINE__);
803
while (cache_list != 0) {
804
dlg_finish_string(cache_list->string_at);
805
}
806
#endif /* USE_CACHING */
807
}
808
#endif /* NO_LEAKS */
809
810