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