1 /*******************************************************************************
2  * Copyright (c) 2013-2021, Andrés Martinelli <andmarti@gmail.com>             *
3  * All rights reserved.                                                        *
4  *                                                                             *
5  * This file is a part of SC-IM                                                *
6  *                                                                             *
7  * SC-IM is a spreadsheet program that is based on SC. The original authors    *
8  * of SC are James Gosling and Mark Weiser, and mods were later added by       *
9  * Chuck Martin.                                                               *
10  *                                                                             *
11  * Redistribution and use in source and binary forms, with or without          *
12  * modification, are permitted provided that the following conditions are met: *
13  * 1. Redistributions of source code must retain the above copyright           *
14  *    notice, this list of conditions and the following disclaimer.            *
15  * 2. Redistributions in binary form must reproduce the above copyright        *
16  *    notice, this list of conditions and the following disclaimer in the      *
17  *    documentation and/or other materials provided with the distribution.     *
18  * 3. All advertising materials mentioning features or use of this software    *
19  *    must display the following acknowledgement:                              *
20  *    This product includes software developed by Andrés Martinelli            *
21  *    <andmarti@gmail.com>.                                                    *
22  * 4. Neither the name of the Andrés Martinelli nor the                        *
23  *   names of other contributors may be used to endorse or promote products    *
24  *   derived from this software without specific prior written permission.     *
25  *                                                                             *
26  * THIS SOFTWARE IS PROVIDED BY ANDRES MARTINELLI ''AS IS'' AND ANY            *
27  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   *
28  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE      *
29  * DISCLAIMED. IN NO EVENT SHALL ANDRES MARTINELLI BE LIABLE FOR ANY           *
30  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  *
31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;*
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
33  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  *
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE       *
35  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
36  *******************************************************************************/
37 
38 /**
39  * \file history.c
40  * \author Andrés Martinelli <andmarti@gmail.com>
41  * \date 2017-07-18
42  * \brief TODO Write a tbrief file description.
43  */
44 
45 #include <string.h>
46 #include <wchar.h>
47 #include "macros.h"
48 
49 // current command before tab completion
50 static wchar_t curcmd [BUFFERSIZE];
51 
52 /**
53  * \brief TODO Document copy_to_curcmd()
54  *
55  * \param[in] inputline
56  *
57  * \return none
58  */
59 
copy_to_curcmd(wchar_t * inputline)60 void copy_to_curcmd(wchar_t * inputline) {
61     wcscpy(curcmd, inputline);
62 }
63 
64 /**
65  * \brief TODO Document get_curcmd()
66  *
67  * \return curcmd
68  */
69 
get_curcmd()70 wchar_t * get_curcmd() {
71     return curcmd;
72 }
73 
74 // this comp mark is used to mark when tab completion is started
75 static int comp = 0;
76 
77 /**
78  * \brief TODO Document get_comp()
79  *
80  * \return none
81  */
82 
get_comp()83 int get_comp() {
84     return comp;
85 }
86 
87 /**
88  * \brief TODO Document set_comp()
89  *
90  * \param[in] i
91  *
92  * \return none
93  */
94 
set_comp(int i)95 void set_comp(int i) {
96     comp = i;
97 }
98 
99 #if defined HISTORY_FILE
100 #include <stdlib.h>
101 #include <fcntl.h>
102 #include <unistd.h>
103 #include <sys/stat.h>
104 
105 #include "history.h"
106 #include "sc.h"
107 #include "utils/string.h"
108 
create_history(char mode)109 struct history * create_history(char mode) {
110     struct history * h = (struct history *) malloc (sizeof (struct history));
111     h->len = 0;
112     h->pos = 0;
113     h->mode = mode;
114     h->list = NULL;
115     return h;
116 }
117 
118 /**
119  * \brief TODO Document destroy_history()
120  *
121  * \param[in] h
122  *
123  * \return none
124  */
125 
destroy_history(struct history * h)126 void destroy_history(struct history * h) {
127     struct hlist * nl;
128     struct hlist * n_sig;
129     nl = h->list;
130     while (nl != NULL) {
131         n_sig = nl->pnext;
132         free(nl->line);
133         free(nl);
134         nl = n_sig;
135     }
136     free(h);
137     return;
138 }
139 
140 /**
141  * \brief TODO Document load_history
142  *
143  * \details Read the specified history file from the user's home directory
144  * and load it into the specified history struct.
145  *
146  * \param[in] h
147  * \param[in] mode
148  *
149  * returns: none
150  */
151 
load_history(struct history * h,wchar_t mode)152 void load_history(struct history * h, wchar_t mode) {
153     char infofile[PATHLEN];
154     wchar_t linea[FBUFLEN];
155     int c;
156     char * home;
157     FILE * f;
158 
159     if ((home = getenv("XDG_CACHE_HOME"))) {
160         /* Try XDG_CACHE_HOME first */
161          sprintf(infofile, "%s/%s", home,HISTORY_FILE);
162     } else if ((home = getenv("HOME"))) {
163         /* Fallback */
164         sprintf(infofile, "%s/%s/%s", home,HISTORY_DIR,HISTORY_FILE);
165     }
166 
167     if ((c = open(infofile, O_RDONLY)) > -1) {
168         close(c);
169         f = fopen(infofile, "r");
170         if (f == NULL) return;
171         while ( feof(f) == 0 ) {
172             if (!fgetws(linea, sizeof(linea) / sizeof(*linea), f)) break;
173             int s = wcslen(linea)-1;
174             del_range_wchars(linea, s, s);
175 
176             if (linea[0] == mode && mode == L':') {
177                 del_range_wchars(linea, 0, 0);
178                 add(h, linea);
179             } else if (mode != L':' && (linea[0] == L'=' || linea[0] == L'<' || linea[0] == L'>' || linea[0] == L'\\')) {
180                 add(h, linea);
181             }
182         }
183         fclose(f);
184     }
185 
186     return;
187 }
188 
189 /**
190  * @brief Save history to file
191  *
192  * \return 0 on success; -1 otherwise
193  */
194 
save_history(struct history * h,char * mode)195 int save_history(struct history * h, char * mode) {
196     char infofile [PATHLEN];
197     char * home;
198     FILE * f;
199     int i;
200     struct hlist * nl = h->list;
201     char history_dir[PATHLEN-(sizeof HISTORY_FILE)];
202 
203     if ((home = getenv("XDG_CACHE_HOME"))) {
204         snprintf(history_dir, PATHLEN-(sizeof HISTORY_FILE), "%s", home);
205         mkdir(history_dir,0777);
206         snprintf(infofile, PATHLEN, "%s/%s", history_dir, HISTORY_FILE);
207     } else if ((home = getenv("HOME"))) {
208         snprintf(history_dir, PATHLEN-(sizeof HISTORY_FILE), "%s/%s", home, HISTORY_DIR);
209         mkdir(history_dir,0777);
210         snprintf(infofile, PATHLEN, "%s/%s", history_dir, HISTORY_FILE);
211     } else {
212         /* If both HOME and XDG_CACHE_HOME aren't set, abandon all hope */
213         return 0;
214     }
215 
216     f = fopen(infofile, mode);
217     if (f == NULL) return 0;
218     // Go to the end
219     for (i=1; i < h->len; i++) {
220         nl = nl->pnext;
221     }
222     // Traverse list back to front, so the history is saved in chronological order
223     for (i=0; i < h->len; i++) {
224         if (! strcmp(mode, "w")) fwprintf(f, L":"); // mode 'w' means we are saving the command mode history
225         fwprintf(f, L"%ls\n", nl->line);
226         nl = nl->pant;
227     }
228     fclose(f);
229     return 1;
230 }
231 
232 /**
233  * \brief Remove history element
234  *
235  * \return 0 first element; -1 second element
236  */
237 
del_item_from_history(struct history * h,int pos)238 void del_item_from_history(struct history * h, int pos) {
239     if (h->len - 1 < -pos) return;
240 
241     struct hlist * nl = h->list;
242     struct hlist * n_ant = NULL;
243     int i;
244 
245     if (pos == 0) {
246         h->list = nl->pnext;
247         if (nl->pnext != NULL) nl->pnext->pant = NULL;
248     } else {
249         for (i=0; i<-pos; i++) {
250             n_ant = nl;
251             nl = nl->pnext;
252         }
253         n_ant->pnext = nl->pnext;
254         if (nl->pnext != NULL) nl->pnext->pant = n_ant;
255     }
256     free(nl->line);
257     free(nl);
258     h->len--;
259 
260     return;
261 }
262 
263 /**
264  * \brief TODO <brief function description>
265  *
266  * \details Find a history element and move it. Starts from POS
267  * pos=0 first element, pos=-1 second element. Returns 1 if moved,
268  * 0 otherwise
269  *
270  * \param[in] h
271  * \param[in] item
272  * \param[in] pos
273  *
274  * \returns: 1 if moved; 0 otherwise
275  */
276 
move_item_from_history_by_str(struct history * h,wchar_t * item,int pos)277 int move_item_from_history_by_str(struct history * h, wchar_t * item, int pos) {
278     if (h->len - 1 < -pos || pos == 0 || ! wcslen(item)) return 0; // Move the first element is not allowed
279     struct hlist * nl = h->list;
280     if (nl != NULL && !wcscmp(item, nl->line)) return 1;
281     struct hlist * n_ant = NULL;
282     int i;
283 
284     for (i=0; i<-pos; i++) {
285         n_ant = nl;
286         nl = nl->pnext;
287     }
288     for (i=-pos; i < h->len ; i++) {
289         if (wcscmp(item, nl->line) == 0) break;
290         n_ant = nl;
291         nl = nl->pnext;
292     }
293     if (i >= h->len) return 0;
294     n_ant->pnext = nl->pnext;
295     if (nl->pnext != NULL) nl->pnext->pant = n_ant;
296 
297     nl->pant = NULL;
298     nl->pnext = h->list;
299     h->list->pant = nl;
300     h->list = nl;
301 
302     return 1;
303 }
304 
305 /**
306  * @brief Add recent entry at the beginning
307  *
308  * \param[in] h
309  * \param[in] line
310  *
311  * \return none
312  */
313 
add(struct history * h,wchar_t * line)314 void add(struct history * h, wchar_t * line) {
315     struct hlist * nl = (struct hlist *) malloc(sizeof(struct hlist));
316 
317     // Save the line
318     int size = wcslen(line)+1;
319     if (size == 1) size = BUFFERSIZE + 1;
320 
321     wchar_t * val = (wchar_t *) malloc(sizeof(wchar_t) * size);
322 
323     val[0]=L'\0';
324     wcscpy(val, line);
325     nl->line = val;
326 
327     // Append at the beginning
328     nl->pant = NULL;
329     nl->pnext = h->list;
330     if (h->list != NULL) h->list->pant = nl;
331     h->list = nl;
332     h->len++;
333 
334     return;
335 }
336 
337 /**
338  * \brief Returns
339  *
340  * \details Returns a history line form COMMAND_MODE
341  * POS 0 is the mose recent line
342  *
343  * \return none
344  */
345 
get_line_from_history(struct history * h,int pos)346 wchar_t * get_line_from_history(struct history * h, int pos) {
347     return get_hlist_from_history(h, pos)->line;
348 }
349 
350 /**
351  * \brief TODO Document get_hlist_from_history()
352  *
353  * \param[in] h
354  * \param[in] pos
355  *
356  * \return none
357  */
358 
get_hlist_from_history(struct history * h,int pos)359 struct hlist * get_hlist_from_history(struct history * h, int pos) {
360     if (h->len <= - pos) return NULL;
361     int i;
362     struct hlist * nl = h->list;
363 
364     for (i=0; i<-pos; i++) {
365         nl = nl->pnext;
366     }
367     return nl;
368 }
369 #endif
370