1 /*
2
3 history.c
4
5 is part of:
6
7 WinEditLine (formerly MinGWEditLine)
8 Copyright 2010-2020 Paolo Tosco <paolo.tosco.mail@gmail.com>
9 All rights reserved.
10
11 Redistribution and use in source and binary forms, with or without
12 modification, are permitted provided that the following conditions
13 are met:
14
15 * Redistributions of source code must retain the above copyright
16 notice, this list of conditions and the following disclaimer.
17 * Redistributions in binary form must reproduce the above copyright
18 notice, this list of conditions and the following disclaimer in the
19 documentation and/or other materials provided with the distribution.
20 * Neither the name of WinEditLine (formerly MinGWEditLine) nor the
21 name of its contributors may be used to endorse or promote products
22 derived from this software without specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
25 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
30 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
36 */
37
38
39 #define _UNICODE
40 #define UNICODE
41
42 #include <editline/readline.h>
43 #include <editline/wineditline.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <ctype.h>
48 #include <errno.h>
49 #include <tchar.h>
50
51
52 HISTORY_STATE _el_hs = { 0 };
53 extern size_t _el_line_buffer_size;
54
55
56 /*
57 remove excess spaces (tabs, linefeeds...)
58 at the end of line
59 */
_el_remove_tail_spaces(char * line)60 void _el_remove_tail_spaces(char *line)
61 {
62 int len;
63
64
65 len = (int)strlen(line);
66 while (len && isspace(line[len - 1])) {
67 --len;
68 }
69 line[len] = '\0';
70 }
71
72
73 /*
74 read the .editrc file
75 */
source_editrc()76 void source_editrc()
77 {
78 wchar_t *appdata = NULL;
79 wchar_t *editrc = NULL;
80 wchar_t string[_EL_ENV_BUF_LEN];
81 char line[_EL_ENV_BUF_LEN];
82 char s1[_EL_ENV_BUF_LEN];
83 char s2[_EL_ENV_BUF_LEN];
84 int d1 = -1;
85 size_t n;
86 FILE *handle = NULL;
87
88
89 _wgetenv_s(&n, NULL, 0, _T("EDITRC"));
90 if (n) {
91 if (!(editrc = malloc((n + 1) * sizeof(wchar_t)))) {
92 return;
93 }
94 _wgetenv_s(&n, editrc, n, _T("EDITRC"));
95 if (!n) {
96 free(editrc);
97 }
98 }
99 if (!n) {
100 /*
101 if the EDITRC environment variable is not set
102 look for %APPDATA%\.editrc
103 */
104 _wgetenv_s(&n, NULL, 0, _T("APPDATA"));
105 if (n) {
106 if (!(appdata = malloc((n + 1) * sizeof(wchar_t)))) {
107 return;
108 }
109 _wgetenv_s(&n, appdata, n, _T("APPDATA"));
110 }
111 if (!n) {
112 return;
113 }
114 n = wcslen(appdata) + _EL_ENV_BUF_LEN;
115 if (!(editrc = malloc(n * sizeof(wchar_t)))) {
116 return;
117 }
118 swprintf_s(editrc, n, _T("%s\\.editrc"), appdata);
119 free(appdata);
120 }
121 if (_wfopen_s(&handle, editrc, _T("r"))) {
122 free(editrc);
123 return;
124 }
125 /*
126 if .editrc could be opened,
127 look for the "history size" line and
128 read the value
129 */
130 while (fgets(line, _EL_ENV_BUF_LEN, handle)) {
131 line[_EL_ENV_BUF_LEN - 1] = '\0';
132 if (line[0]) {
133 sscanf_s(line, "%16s %16s %d",
134 s1, _EL_ENV_BUF_LEN, s2, _EL_ENV_BUF_LEN, &d1);
135 if ((!_stricmp(s1, "history"))
136 && (!_stricmp(s2, "size"))) {
137 if (d1 < _EL_MIN_HIST_SIZE) {
138 d1 = _EL_MIN_HIST_SIZE;
139 }
140 }
141 }
142 }
143 fclose(handle);
144 /*
145 if a valid value has been found, set the
146 MINGWEDITLINE_HISTORY_SIZE environment variable
147 to this value
148 */
149 if (d1 != -1) {
150 swprintf_s(string, _EL_ENV_BUF_LEN, _T("MINGWEDITLINE_HISTORY_SIZE=%d"), d1);
151 _wputenv(string);
152 }
153 free(editrc);
154 }
155
156
157 /*
158 this function MUST be called before using history functions
159 returns 0 if successful, -1 if not
160 */
using_history()161 int using_history()
162 {
163 wchar_t *string_size;
164 int size = DEFAULT_HISTORY_SIZE;
165 int temp_size;
166 size_t n;
167
168
169 /*
170 try to load .editrc; if it does not exist
171 or is malformed, use the default history size
172 */
173 source_editrc();
174 _wgetenv_s(&n, NULL, 0, _T("MINGWEDITLINE_HISTORY_SIZE"));
175 if (n) {
176 if ((string_size = malloc((n + 1) * sizeof(wchar_t)))) {
177 _wgetenv_s(&n, string_size, n, _T("MINGWEDITLINE_HISTORY_SIZE"));
178 }
179 else {
180 n = 0;
181 }
182 }
183 if (n) {
184 swscanf_s(string_size, _T("%d"), &temp_size);
185 if (temp_size >= _EL_MIN_HIST_SIZE) {
186 size = temp_size;
187 }
188 }
189 /*
190 initialize history
191 */
192 memset(&_el_hs, 0, sizeof(HISTORY_STATE));
193 _el_hs.entries = (HIST_ENTRY **)_el_alloc_array
194 (size + 1, sizeof(HIST_ENTRY));
195 if (!_el_hs.entries) {
196 return -1;
197 }
198 _el_hs.length = 1;
199 _el_hs.size = size;
200
201 return 0;
202 }
203
204
205 /*
206 this function may be called after calling
207 readline() for the last time
208 if you do, remember to call again
209 using_history() before calling readline()
210 */
free_history()211 void free_history()
212 {
213 int i;
214
215
216 if (_el_hs.entries) {
217 for (i = 0; i <= _el_hs.size; ++i) {
218 if (_el_hs.entries[i]) {
219 free(_el_hs.entries[i]->line);
220 _el_hs.entries[i]->line = NULL;
221 }
222 }
223 _el_free_array(_el_hs.entries);
224 }
225 }
226
227
228 /*
229 this function reads history from a text
230 file; if filename is a NULL pointer, then
231 %APPDATA%\.history is searched
232 returns 0 if successful, -1 or errno if not
233 */
read_history(const char * filename)234 int read_history(const char *filename)
235 {
236 wchar_t *name = NULL;
237 char *line = NULL;
238 char *eof;
239 int grow_line;
240 int line_size = 0;
241 int s = 0;
242 int i;
243 int line_len = 0;
244 FILE *file;
245
246
247 if (!_el_hs.entries) {
248 if (using_history()) {
249 return -1;
250 }
251 }
252 errno = 0;
253 if (_el_find_history_file(filename, &name)) {
254 return -1;
255 }
256 if (_wfopen_s(&file, name, _T("r"))) {
257 return errno;
258 }
259 i = 0;
260 grow_line = 1;
261 do {
262 if (grow_line) {
263 line_size += _EL_BUF_LEN;
264 line = realloc(line, line_size);
265 if (!line) {
266 return -1;
267 }
268 }
269 memset(&line[s], 0, _EL_BUF_LEN);
270 eof = fgets(&line[s], _EL_BUF_LEN, file);
271 line_len = (int)strlen(line);
272 s = 0;
273 grow_line = (eof && line_len && (line[line_len - 1] != '\n'));
274 if (grow_line) {
275 s = line_len;
276 }
277 else {
278 _el_remove_tail_spaces(line);
279 if (line[0]) {
280 ++i;
281 }
282 }
283 } while (eof);
284 rewind(file);
285 /*
286 skip the older history lines until the
287 remaining do not fit into _el_hs.size
288 */
289 while (i > _el_hs.size) {
290 if (!fgets(line, line_size, file)) {
291 break;
292 }
293 _el_remove_tail_spaces(line);
294 if (line[0]) {
295 --i;
296 }
297 }
298 i = 0;
299 while (fgets(line, line_size, file)) {
300 _el_remove_tail_spaces(line);
301 if (line[0]) {
302 line_len = (int)strlen(line);
303 _el_hs.entries[i]->line = realloc
304 (_el_hs.entries[i]->line, line_len + 1);
305 if (!_el_hs.entries[i]->line) {
306 return -1;
307 }
308 strcpy_s(_el_hs.entries[i]->line, line_len + 1, line);
309 ++i;
310 ++_el_hs.length;
311 ++_el_hs.offset;
312 }
313 }
314 fclose(file);
315 free(name);
316 free(line);
317
318 return 0;
319 }
320
321
322 /*
323 this function writes history to a text
324 file; if filename is a NULL pointer, then
325 %APPDATA%\.history is written
326 returns 0 if successful, -1 or errno if not
327 */
_el_write_history_helper(const char * filename,const wchar_t * mode,int nelements)328 int _el_write_history_helper(const char *filename, const wchar_t *mode, int nelements)
329 {
330 wchar_t *name = NULL;
331 int i;
332 FILE *file = NULL;
333
334
335 errno = 0;
336 if (!_el_hs.entries
337 || _el_find_history_file(filename, &name)) {
338 return -1;
339 }
340 if (_wfopen_s(&file, name, mode)) {
341 return errno;
342 }
343 for (i = ((nelements == -1) || ((nelements + 1) > _el_hs.length))
344 ? 0 : _el_hs.length - (nelements + 1); i < (_el_hs.length - 1); ++i) {
345 if (_el_hs.entries[i]->line[0]) {
346 fprintf(file, "%s\n", _el_hs.entries[i]->line);
347 }
348 }
349 fclose(file);
350 free(name);
351
352 return 0;
353 }
354
355
356 /*
357 this function writes history to a text file; if filename is
358 a NULL pointer, then %APPDATA%\.history is written
359 returns 0 if successful, -1 or errno if not
360 */
write_history(const char * filename)361 int write_history(const char *filename)
362 {
363 return _el_write_history_helper(filename, _T("w"), -1);
364 }
365
366
367 /*
368 this function appends the last nelements of history to a text file;
369 if filename is a NULL pointer, then %APPDATA%\.history is written
370 returns 0 if successful, -1 or errno if not
371 */
append_history(int nelements,const char * filename)372 int append_history(int nelements, const char *filename)
373 {
374 return _el_write_history_helper(filename, _T("a"), nelements);
375 }
376
377
378 /*
379 this function truncates the history file "filename", leaving only
380 the last nlines lines; if filename is a NULL pointer, then
381 %APPDATA%\.history is used
382 returns 0 if successful, -1 or errno if not
383 */
history_truncate_file(const char * filename,int nlines)384 int history_truncate_file(const char *filename, int nlines)
385 {
386 int ret = -1;
387 HISTORY_STATE _el_hs_copy = _el_hs;
388
389 memset(&_el_hs, 0, sizeof(HISTORY_STATE));
390 if (!read_history(filename)) {
391 ret = _el_write_history_helper(filename, _T("w"), nlines);
392 }
393 free_history();
394 _el_hs = _el_hs_copy;
395
396 return ret;
397 }
398
399
400 /*
401 this function frees a history entry
402 */
free_history_entry(HIST_ENTRY * entry)403 void free_history_entry(HIST_ENTRY *entry)
404 {
405 if (entry) {
406 free(entry->line);
407 free(entry);
408 }
409 }
410
411
412 /*
413 this function clears current history
414 */
clear_history()415 void clear_history()
416 {
417 int i;
418
419
420 if (!_el_hs.entries) {
421 return;
422 }
423 for (i = 0; i < _el_hs.length; ++i) {
424 if (_el_hs.entries[i]) {
425 free(_el_hs.entries[i]->line);
426 _el_hs.entries[i]->line = NULL;
427 }
428 }
429 _el_hs.length = 1;
430 _el_hs.offset = 0;
431 }
432
433
434 /*
435 this function adds line to history;
436 returns a pointer to the newly added
437 string, or NULL in case of error
438 */
add_history(char * line)439 char *add_history(char *line)
440 {
441 int len;
442 HIST_ENTRY *temp;
443
444
445 if (!line) {
446 return NULL;
447 }
448 len = (int)strlen(line);
449 if (!_el_hs.entries) {
450 if (using_history()) {
451 return NULL;
452 }
453 }
454 if (!_el_hs.entries) {
455 return NULL;
456 }
457 if (_el_hs.length > _el_hs.size) {
458 temp = _el_hs.entries[0];
459 memmove(&_el_hs.entries[0], &_el_hs.entries[1],
460 _el_hs.size * sizeof(HIST_ENTRY *));
461 _el_hs.entries[_el_hs.size] = temp;
462 --_el_hs.length;
463 }
464 _el_hs.entries[_el_hs.length - 1]->line = realloc
465 (_el_hs.entries[_el_hs.length - 1]->line, len + 1);
466 if (!_el_hs.entries[_el_hs.length - 1]->line) {
467 return NULL;
468 }
469 strcpy_s(_el_hs.entries[_el_hs.length - 1]->line, len + 1, line);
470 if (_el_hs.entries[_el_hs.length]->line) {
471 _el_hs.entries[_el_hs.length]->line[0] = '\0';
472 }
473 _el_hs.offset = _el_hs.length;
474 ++_el_hs.length;
475
476 return _el_hs.entries[_el_hs.length - 2]->line;
477 }
478
479
480 /*
481 this function removes a history entry
482 identified by its progressive index
483 (starting from zero)
484 returns the old entry so the user can
485 free the memory allocated to the string
486 and to the HIST_ENTRY structure itself,
487 or NULL if not successful
488 */
remove_history(int i)489 HIST_ENTRY *remove_history(int i)
490 {
491 HIST_ENTRY *temp;
492
493
494 if (!_el_hs.entries || !_el_hs.entries[i]
495 || (i < 0) || (i >= _el_hs.length)) {
496 return NULL;
497 }
498 temp = _el_hs.entries[i];
499 if (i < (_el_hs.length - 1)) {
500 memmove(&_el_hs.entries[i], &_el_hs.entries[i + 1],
501 (_el_hs.length - i - 1) * sizeof(HIST_ENTRY *));
502 }
503 _el_hs.entries[_el_hs.length - 1] =
504 (HIST_ENTRY *)malloc(sizeof(HIST_ENTRY));
505 memset(_el_hs.entries[_el_hs.length - 1], 0, sizeof(HIST_ENTRY));
506
507 return temp;
508 }
509
510
511 /*
512 this function replaces the string content
513 of a history entry identified by its progressive
514 index (starting from zero)
515 the histdata_t is ignored and kept only for
516 compatibility reasons
517 returns a pointer to the updated HIST_ENTRY
518 or NULL if not successful
519 */
replace_history_entry(int i,char * line,histdata_t dummy)520 HIST_ENTRY *replace_history_entry(int i, char *line, histdata_t dummy)
521 {
522 size_t len;
523
524
525 len = strlen(line) + 1;
526 if (!_el_hs.entries || !_el_hs.entries[i]
527 || (i < 0) || (i >= _el_hs.length)) {
528 return NULL;
529 }
530 _el_hs.entries[i]->line = realloc(_el_hs.entries[i]->line, len);
531 if (!_el_hs.entries[i]->line) {
532 return NULL;
533 }
534 strcpy_s(_el_hs.entries[i]->line, len, line);
535
536 return _el_hs.entries[i];
537 }
538
539
540 /*
541 this function returns a pointer
542 to the HIST_ENTRY array
543 */
history_list()544 HIST_ENTRY **history_list()
545 {
546 return _el_hs.entries;
547 }
548
549
550 /*
551 this function returns the current
552 history index
553 */
where_history()554 int where_history()
555 {
556 return _el_hs.offset;
557 }
558
559
560 /*
561 this function returns the current
562 history length
563 */
history_length()564 int history_length()
565 {
566 return _el_hs.length - 1;
567 }
568
569
570 /*
571 this function returns a pointer to
572 the current HIST_ENTRY structure
573 */
current_history()574 HIST_ENTRY *current_history()
575 {
576 return ((_el_hs.entries && _el_hs.offset)
577 ? _el_hs.entries[_el_hs.offset - 1] : NULL);
578 }
579
580
581 /*
582 this function returns a pointer to
583 the HIST_ENTRY structure identified
584 by its progressive index
585 (starting from zero) or NULL if the
586 index i does not exist
587 */
history_get(int i)588 HIST_ENTRY *history_get(int i)
589 {
590 return (!_el_hs.entries || ((i < 0)
591 || (i > _el_hs.length)) ? NULL : _el_hs.entries[i]);
592 }
593
594
595 /*
596 this function sets the current history
597 offset based on the history entry
598 progressive index (starting from zero)
599 returns 1 if succesful, 0 if not
600 */
_el_history_set_pos(int i)601 int _el_history_set_pos(int i)
602 {
603 if ((i < 1) || (i > (_el_hs.length + 1))) {
604 return 0;
605 }
606 _el_hs.offset = i - 1;
607
608 return 1;
609 }
610
611
612 /*
613 this function sets the current history
614 offset based on the history entry
615 progressive index (starting from zero)
616 returns 1 if succesful, 0 if not
617 */
history_set_pos(int i)618 int history_set_pos(int i)
619 {
620 if ((i < 1) || (i > _el_hs.length)) {
621 return 0;
622 }
623 _el_hs.offset = i - 1;
624
625 return 1;
626 }
627
628
629 /*
630 this function returns a pointer to
631 the previous HIST_ENTRY, or NULL
632 if the current offset is already at
633 the beginning of history
634 */
_el_previous_history()635 HIST_ENTRY *_el_previous_history()
636 {
637 return ((_el_hs.entries && _el_hs.offset)
638 ? _el_hs.entries[--_el_hs.offset] : NULL);
639 }
640
641
642 /*
643 this function returns a pointer to
644 the previous HIST_ENTRY, or NULL
645 if the current offset is already at
646 the beginning of history
647 */
previous_history()648 HIST_ENTRY *previous_history()
649 {
650 return ((_el_hs.entries && (_el_hs.offset > 1))
651 ? _el_hs.entries[--_el_hs.offset] : NULL);
652 }
653
654
655 /*
656 this function returns a pointer to the next HIST_ENTRY, or NULL
657 if the current offset is already at the end of history
658 */
_el_next_history()659 HIST_ENTRY *_el_next_history()
660 {
661 return ((((_el_hs.offset == 0) && (_el_hs.length == 1))
662 || (_el_hs.offset == _el_hs.length)
663 || !_el_hs.entries || !_el_hs.entries[_el_hs.offset + 1])
664 ? NULL : _el_hs.entries[++_el_hs.offset]);
665 }
666
667
668 /*
669 this function returns a pointer to the next HIST_ENTRY, or NULL
670 if the current offset is already at the end of history
671 */
next_history()672 HIST_ENTRY *next_history()
673 {
674 return (((_el_hs.offset == _el_hs.length - 1)
675 || !_el_hs.entries || !_el_hs.entries[_el_hs.offset + 1])
676 ? NULL : _el_hs.entries[++_el_hs.offset]);
677 }
678
679
680 /*
681 this function displays the current history entry
682 */
_el_display_history()683 void _el_display_history()
684 {
685 int line_len;
686 int h_len;
687
688
689 _el_set_cursor(-rl_point);
690 if (!_el_hs.entries || (!_el_mb2w
691 (_el_hs.entries[_el_hs.offset]->line, &_el_wide))) {
692 return;
693 }
694 h_len = (int)wcslen(_el_wide);
695 line_len = (int)wcslen(_el_line_buffer);
696 _el_grow_buffers(h_len);
697 wcscpy_s(_el_line_buffer, _el_line_buffer_size, _el_wide);
698 wcscpy_s(_el_print, _el_line_buffer_size, _el_wide);
699 rl_point = (int)h_len;
700 if (h_len < line_len) {
701 _el_add_char(_el_print, _T(' '), line_len - h_len);
702 }
703 _el_print_string(_el_print);
704 _el_set_cursor(h_len);
705 }
706
707
708 /*
709 this function copies the "filename" string to "name"
710 if filename is a NULL pointer, %APPDATA%\.history
711 is copied to "name"
712 */
_el_find_history_file(const char * filename,wchar_t ** name)713 int _el_find_history_file(const char *filename, wchar_t **name)
714 {
715 wchar_t *appdata = NULL;
716 size_t n;
717
718
719 if (!filename) {
720 _wgetenv_s(&n, NULL, 0, _T("APPDATA"));
721 if (n) {
722 if ((appdata = malloc((n + 1) * sizeof(wchar_t)))) {
723 _wgetenv_s(&n, appdata, n, _T("APPDATA"));
724 }
725 else {
726 n = 0;
727 }
728 }
729 if (n) {
730 n = (wcslen(appdata) + _EL_ENV_BUF_LEN);
731 if (!(*name = malloc(n * sizeof(wchar_t)))) {
732 return -1;
733 }
734 swprintf_s(*name, n, _T("%s\\"), appdata);
735 }
736 wcscat_s(*name, n, _T(".history"));
737 }
738 else {
739 if (!_el_mb2w((char *)filename, name)) {
740 return -1;
741 }
742 }
743 free(appdata);
744
745 return 0;
746 }
747