rewrite: update default dumb and smart prefixes
[elinks/elinks-j605.git] / src / bfu / inphist.c
blob8c6d005c598218aa9402aa886f444056f0dcab53
1 /* Input history for input fields. */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
10 #include "elinks.h"
12 #include "bfu/dialog.h"
13 #include "bfu/inphist.h"
14 #include "bfu/menu.h"
15 #include "config/home.h"
16 #include "config/options.h"
17 #include "dialogs/menu.h"
18 #include "terminal/terminal.h"
19 #include "util/conv.h"
20 #include "util/file.h"
21 #include "util/lists.h"
22 #include "util/memory.h"
23 #include "util/secsave.h"
26 static void
27 tab_compl_n(struct dialog_data *dlg_data, unsigned char *item, int len)
29 struct widget_data *widget_data = selected_widget(dlg_data);
31 assert(widget_is_textfield(widget_data));
33 int_upper_bound(&len, widget_data->widget->datalen - 1);
34 memcpy(widget_data->cdata, item, len);
35 widget_data->cdata[len] = 0;
36 widget_data->info.field.cpos = len;
37 widget_data->info.field.vpos = 0;
39 redraw_dialog(dlg_data, 1);
42 static void
43 tab_compl(struct dialog_data *dlg_data, unsigned char *item)
45 tab_compl_n(dlg_data, item, strlen(item));
48 /* menu_func_T */
49 static void
50 menu_tab_compl(struct terminal *term, void *item_, void *dlg_data_)
52 unsigned char *item = item_;
53 struct dialog_data *dlg_data = dlg_data_;
55 tab_compl_n(dlg_data, item, strlen(item));
58 /* Complete to last unambiguous character, and display menu for all possible
59 * further completions. */
60 void
61 do_tab_compl(struct dialog_data *dlg_data,
62 LIST_OF(struct input_history_entry) *history)
64 struct terminal *term = dlg_data->win->term;
65 struct widget_data *widget_data = selected_widget(dlg_data);
66 int cpos = widget_data->info.field.cpos;
67 int n = 0;
68 struct input_history_entry *entry;
69 struct menu_item *items = new_menu(FREE_LIST | NO_INTL);
71 if (!items) return;
73 foreach (entry, *history) {
74 if (strncmp(widget_data->cdata, entry->data, cpos))
75 continue;
77 add_to_menu(&items, entry->data, NULL, ACT_MAIN_NONE,
78 menu_tab_compl, entry->data, 0);
79 n++;
82 if (n > 1) {
83 do_menu_selected(term, items, dlg_data, n - 1, 0);
84 } else {
85 if (n == 1) tab_compl(dlg_data, items->data);
86 mem_free(items);
90 /* Return the length of the common substring from the starts
91 * of the two strings a and b. */
92 static inline int
93 strcommonlen(unsigned char *a, unsigned char *b)
95 unsigned char *start = a;
97 while (*a && *a == *b)
98 ++a, ++b;
100 return a - start;
103 /* Complete to the last unambiguous character. Eg., I've been to google.com,
104 * google.com/search?q=foo, and google.com/search?q=bar. This function then
105 * completes `go' to `google.com' and `google.com/' to `google.com/search?q='.
107 void
108 do_tab_compl_unambiguous(struct dialog_data *dlg_data,
109 LIST_OF(struct input_history_entry) *history)
111 struct string completion;
112 struct widget_data *widget_data = selected_widget(dlg_data);
113 int base_len = widget_data->info.field.cpos;
114 /* Maximum number of characters in a match. Characters after this
115 * position are varying in other matches. */
116 int longest_common_match = 0;
117 unsigned char *match = NULL;
118 struct input_history_entry *entry;
120 foreach (entry, *history) {
121 unsigned char *cur = entry->data;
122 int cur_len = strcommonlen(cur, match ? match
123 : widget_data->cdata);
125 /* Throw away it away if it isn't even as long as what the user
126 * entered. */
127 if (cur_len < base_len)
128 continue;
130 if (!match) {
131 /* This is the first match, so its length is the maximum
132 * for any future matches. */
133 longest_common_match = strlen(entry->data);
134 match = entry->data;
135 } else if (cur_len < longest_common_match) {
136 /* The current match has a shorter substring in common
137 * with the previous candidates, so the common substring
138 * shrinks. */
139 longest_common_match = cur_len;
143 if (!match) return;
145 if (!init_string(&completion)) return;
147 add_bytes_to_string(&completion, match, longest_common_match);
148 add_to_string(&completion, widget_data->cdata
149 + widget_data->info.field.cpos);
151 tab_compl_n(dlg_data, completion.source, completion.length);
153 done_string(&completion);
157 /* menu_func_T */
158 static void
159 set_complete_file_menu(struct terminal *term, void *filename_, void *dlg_data_)
161 struct dialog_data *dlg_data = dlg_data_;
162 struct widget_data *widget_data = selected_widget(dlg_data);
163 unsigned char *filename = filename_;
164 int filenamelen;
166 assert(widget_is_textfield(widget_data));
168 filenamelen = int_min(widget_data->widget->datalen - 1, strlen(filename));
169 memcpy(widget_data->cdata, filename, filenamelen);
171 widget_data->cdata[filenamelen] = 0;
172 widget_data->info.field.cpos = filenamelen;
173 widget_data->info.field.vpos = 0;
175 mem_free(filename);
177 redraw_dialog(dlg_data, 1);
180 /* menu_func_T */
181 static void
182 tab_complete_file_menu(struct terminal *term, void *path_, void *dlg_data_)
184 struct dialog_data *dlg_data = dlg_data_;
185 unsigned char *path = path_;
187 auto_complete_file(term, 0 /* no_elevator */, path,
188 set_complete_file_menu, tab_complete_file_menu,
189 dlg_data);
192 void
193 do_tab_compl_file(struct dialog_data *dlg_data,
194 LIST_OF(struct input_history_entry) *history)
196 struct widget_data *widget_data = selected_widget(dlg_data);
198 if (get_cmd_opt_bool("anonymous"))
199 return;
201 tab_complete_file_menu(dlg_data->win->term, widget_data->cdata, dlg_data);
205 /* Search for duplicate entries in history list, save first one and remove
206 * older ones. */
207 static struct input_history_entry *
208 check_duplicate_entries(struct input_history *history, unsigned char *data)
210 struct input_history_entry *entry, *first_duplicate = NULL;
212 if (!history || !data || !*data) return NULL;
214 foreach (entry, history->entries) {
215 struct input_history_entry *duplicate;
217 if (strcmp(entry->data, data)) continue;
219 /* Found a duplicate -> remove it from history list */
221 duplicate = entry;
222 entry = entry->prev;
224 del_from_history_list(history, duplicate);
226 /* Save the first duplicate entry */
227 if (!first_duplicate) {
228 first_duplicate = duplicate;
229 } else {
230 mem_free(duplicate);
234 return first_duplicate;
237 /* Add a new entry in inputbox history list, take care of duplicate if
238 * check_duplicate and respect history size limit. */
239 void
240 add_to_input_history(struct input_history *history, unsigned char *data,
241 int check_duplicate)
243 struct input_history_entry *entry;
244 int length;
246 if (!history || !data || !*data)
247 return;
249 /* Strip spaces at the margins */
250 trim_chars(data, ' ', &length);
251 if (!length) return;
253 if (check_duplicate) {
254 entry = check_duplicate_entries(history, data);
255 if (entry) {
256 add_to_history_list(history, entry);
257 return;
261 /* Copy it all etc. */
262 /* One byte is already reserved for url in struct input_history_item. */
263 entry = mem_alloc(sizeof(*entry) + length);
264 if (!entry) return;
266 memcpy(entry->data, data, length + 1);
268 add_to_history_list(history, entry);
270 /* Limit size of history to MAX_INPUT_HISTORY_ENTRIES, removing first
271 * entries if needed. */
272 while (history->size > MAX_INPUT_HISTORY_ENTRIES) {
273 if (list_empty(history->entries)) {
274 INTERNAL("history is empty");
275 history->size = 0;
276 return;
279 entry = history->entries.prev;
280 del_from_history_list(history, entry);
281 mem_free(entry);
285 /* Load history file */
287 load_input_history(struct input_history *history, unsigned char *filename)
289 unsigned char *history_file = filename;
290 unsigned char line[MAX_STR_LEN];
291 FILE *file;
293 if (get_cmd_opt_bool("anonymous")) return 0;
294 if (elinks_home) {
295 history_file = straconcat(elinks_home, filename,
296 (unsigned char *) NULL);
297 if (!history_file) return 0;
300 file = fopen(history_file, "rb");
301 if (elinks_home) mem_free(history_file);
302 if (!file) return 0;
304 history->nosave = 1;
306 while (fgets(line, MAX_STR_LEN, file)) {
307 /* Drop '\n'. */
308 if (*line) line[strlen(line) - 1] = 0;
309 add_to_input_history(history, line, 0);
312 history->nosave = 0;
314 fclose(file);
316 return 0;
319 /* Write history list to file. It returns a value different from 0 in case of
320 * failure, 0 on success. */
322 save_input_history(struct input_history *history, unsigned char *filename)
324 struct input_history_entry *entry;
325 struct secure_save_info *ssi;
326 unsigned char *history_file;
327 int i = 0;
329 if (!history->dirty
330 || !elinks_home
331 || get_cmd_opt_bool("anonymous"))
332 return 0;
334 history_file = straconcat(elinks_home, filename,
335 (unsigned char *) NULL);
336 if (!history_file) return -1;
338 ssi = secure_open(history_file);
339 mem_free(history_file);
340 if (!ssi) return -1;
342 foreachback (entry, history->entries) {
343 if (i++ > MAX_INPUT_HISTORY_ENTRIES) break;
344 secure_fputs(ssi, entry->data);
345 secure_fputc(ssi, '\n');
346 if (ssi->err) break;
349 if (!ssi->err) history->dirty = 0;
351 return secure_close(ssi);
354 void
355 dlg_set_history(struct widget_data *widget_data)
357 assert(widget_has_history(widget_data));
358 assert(widget_data->widget->datalen > 0);
360 if ((void *) widget_data->info.field.cur_hist != &widget_data->info.field.history) {
361 unsigned char *s = widget_data->info.field.cur_hist->data;
363 widget_data->info.field.cpos = int_min(strlen(s), widget_data->widget->datalen - 1);
364 if (widget_data->info.field.cpos)
365 memcpy(widget_data->cdata, s, widget_data->info.field.cpos);
366 } else {
367 widget_data->info.field.cpos = 0;
370 widget_data->cdata[widget_data->info.field.cpos] = 0;
371 widget_data->info.field.vpos = int_max(0, widget_data->info.field.cpos - widget_data->box.width);