1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-12-04 14:46:47 -05:00
elinks/src/bfu/inphist.c
2007-07-26 22:47:23 +03:00

373 lines
9.2 KiB
C

/* Input history for input fields. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include "elinks.h"
#include "bfu/dialog.h"
#include "bfu/inphist.h"
#include "bfu/menu.h"
#include "config/home.h"
#include "config/options.h"
#include "dialogs/menu.h"
#include "terminal/terminal.h"
#include "util/conv.h"
#include "util/file.h"
#include "util/lists.h"
#include "util/memory.h"
#include "util/secsave.h"
static void
tab_compl_n(struct dialog_data *dlg_data, unsigned char *item, int len)
{
struct widget_data *widget_data = selected_widget(dlg_data);
assert(widget_is_textfield(widget_data));
int_upper_bound(&len, widget_data->widget->datalen - 1);
memcpy(widget_data->cdata, item, len);
widget_data->cdata[len] = 0;
widget_data->info.field.cpos = len;
widget_data->info.field.vpos = 0;
redraw_dialog(dlg_data, 1);
}
static void
tab_compl(struct dialog_data *dlg_data, unsigned char *item)
{
tab_compl_n(dlg_data, item, strlen(item));
}
/* menu_func_T */
static void
menu_tab_compl(struct terminal *term, void *item_, void *dlg_data_)
{
unsigned char *item = item_;
struct dialog_data *dlg_data = dlg_data_;
tab_compl_n(dlg_data, item, strlen(item));
}
/* Complete to last unambiguous character, and display menu for all possible
* further completions. */
void
do_tab_compl(struct dialog_data *dlg_data,
LIST_OF(struct input_history_entry) *history)
{
struct terminal *term = dlg_data->win->term;
struct widget_data *widget_data = selected_widget(dlg_data);
int cpos = widget_data->info.field.cpos;
int n = 0;
struct input_history_entry *entry;
struct menu_item *items = new_menu(FREE_LIST | NO_INTL);
if (!items) return;
foreach (entry, *history) {
if (strncmp(widget_data->cdata, entry->data, cpos))
continue;
add_to_menu(&items, entry->data, NULL, ACT_MAIN_NONE,
menu_tab_compl, entry->data, 0);
n++;
}
if (n > 1) {
do_menu_selected(term, items, dlg_data, n - 1, 0);
} else {
if (n == 1) tab_compl(dlg_data, items->data);
mem_free(items);
}
}
/* Return the length of the common substring from the starts
* of the two strings a and b. */
static inline int
strcommonlen(unsigned char *a, unsigned char *b)
{
unsigned char *start = a;
while (*a && *a == *b)
++a, ++b;
return a - start;
}
/* Complete to the last unambiguous character. Eg., I've been to google.com,
* google.com/search?q=foo, and google.com/search?q=bar. This function then
* completes `go' to `google.com' and `google.com/' to `google.com/search?q='.
*/
void
do_tab_compl_unambiguous(struct dialog_data *dlg_data,
LIST_OF(struct input_history_entry) *history)
{
struct string completion;
struct widget_data *widget_data = selected_widget(dlg_data);
int base_len = widget_data->info.field.cpos;
/* Maximum number of characters in a match. Characters after this
* position are varying in other matches. */
int longest_common_match = 0;
unsigned char *match = NULL;
struct input_history_entry *entry;
foreach (entry, *history) {
unsigned char *cur = entry->data;
int cur_len = strcommonlen(cur, match ? match
: widget_data->cdata);
/* Throw away it away if it isn't even as long as what the user
* entered. */
if (cur_len < base_len)
continue;
if (!match) {
/* This is the first match, so its length is the maximum
* for any future matches. */
longest_common_match = strlen(entry->data);
match = entry->data;
} else if (cur_len < longest_common_match) {
/* The current match has a shorter substring in common
* with the previous candidates, so the common substring
* shrinks. */
longest_common_match = cur_len;
}
}
if (!match) return;
if (!init_string(&completion)) return;
add_bytes_to_string(&completion, match, longest_common_match);
add_to_string(&completion, widget_data->cdata
+ widget_data->info.field.cpos);
tab_compl_n(dlg_data, completion.source, completion.length);
done_string(&completion);
}
/* menu_func_T */
static void
set_complete_file_menu(struct terminal *term, void *filename_, void *dlg_data_)
{
struct dialog_data *dlg_data = dlg_data_;
struct widget_data *widget_data = selected_widget(dlg_data);
unsigned char *filename = filename_;
int filenamelen;
assert(widget_is_textfield(widget_data));
filenamelen = int_min(widget_data->widget->datalen - 1, strlen(filename));
memcpy(widget_data->cdata, filename, filenamelen);
widget_data->cdata[filenamelen] = 0;
widget_data->info.field.cpos = filenamelen;
widget_data->info.field.vpos = 0;
mem_free(filename);
redraw_dialog(dlg_data, 1);
}
/* menu_func_T */
static void
tab_complete_file_menu(struct terminal *term, void *path_, void *dlg_data_)
{
struct dialog_data *dlg_data = dlg_data_;
unsigned char *path = path_;
auto_complete_file(term, 0 /* no_elevator */, path,
set_complete_file_menu, tab_complete_file_menu,
dlg_data);
}
void
do_tab_compl_file(struct dialog_data *dlg_data,
LIST_OF(struct input_history_entry) *history)
{
struct widget_data *widget_data = selected_widget(dlg_data);
if (get_cmd_opt_bool("anonymous"))
return;
tab_complete_file_menu(dlg_data->win->term, widget_data->cdata, dlg_data);
}
/* Search for duplicate entries in history list, save first one and remove
* older ones. */
static struct input_history_entry *
check_duplicate_entries(struct input_history *history, unsigned char *data)
{
struct input_history_entry *entry, *first_duplicate = NULL;
if (!history || !data || !*data) return NULL;
foreach (entry, history->entries) {
struct input_history_entry *duplicate;
if (strcmp(entry->data, data)) continue;
/* Found a duplicate -> remove it from history list */
duplicate = entry;
entry = entry->prev;
del_from_history_list(history, duplicate);
/* Save the first duplicate entry */
if (!first_duplicate) {
first_duplicate = duplicate;
} else {
mem_free(duplicate);
}
}
return first_duplicate;
}
/* Add a new entry in inputbox history list, take care of duplicate if
* check_duplicate and respect history size limit. */
void
add_to_input_history(struct input_history *history, unsigned char *data,
int check_duplicate)
{
struct input_history_entry *entry;
int length;
if (!history || !data || !*data)
return;
/* Strip spaces at the margins */
trim_chars(data, ' ', &length);
if (!length) return;
if (check_duplicate) {
entry = check_duplicate_entries(history, data);
if (entry) {
add_to_history_list(history, entry);
return;
}
}
/* Copy it all etc. */
/* One byte is already reserved for url in struct input_history_item. */
entry = mem_alloc(sizeof(*entry) + length);
if (!entry) return;
memcpy(entry->data, data, length + 1);
add_to_history_list(history, entry);
/* Limit size of history to MAX_INPUT_HISTORY_ENTRIES, removing first
* entries if needed. */
while (history->size > MAX_INPUT_HISTORY_ENTRIES) {
if (list_empty(history->entries)) {
INTERNAL("history is empty");
history->size = 0;
return;
}
entry = history->entries.prev;
del_from_history_list(history, entry);
mem_free(entry);
}
}
/* Load history file */
int
load_input_history(struct input_history *history, unsigned char *filename)
{
unsigned char *history_file = filename;
unsigned char line[MAX_STR_LEN];
FILE *file;
if (get_cmd_opt_bool("anonymous")) return 0;
if (elinks_home) {
history_file = straconcat(elinks_home, filename,
(unsigned char *) NULL);
if (!history_file) return 0;
}
file = fopen(history_file, "rb");
if (elinks_home) mem_free(history_file);
if (!file) return 0;
history->nosave = 1;
while (fgets(line, MAX_STR_LEN, file)) {
/* Drop '\n'. */
if (*line) line[strlen(line) - 1] = 0;
add_to_input_history(history, line, 0);
}
history->nosave = 0;
fclose(file);
return 0;
}
/* Write history list to file. It returns a value different from 0 in case of
* failure, 0 on success. */
int
save_input_history(struct input_history *history, unsigned char *filename)
{
struct input_history_entry *entry;
struct secure_save_info *ssi;
unsigned char *history_file;
int i = 0;
if (!history->dirty
|| !elinks_home
|| get_cmd_opt_bool("anonymous"))
return 0;
history_file = straconcat(elinks_home, filename,
(unsigned char *) NULL);
if (!history_file) return -1;
ssi = secure_open(history_file);
mem_free(history_file);
if (!ssi) return -1;
foreachback (entry, history->entries) {
if (i++ > MAX_INPUT_HISTORY_ENTRIES) break;
secure_fputs(ssi, entry->data);
secure_fputc(ssi, '\n');
if (ssi->err) break;
}
if (!ssi->err) history->dirty = 0;
return secure_close(ssi);
}
void
dlg_set_history(struct widget_data *widget_data)
{
assert(widget_has_history(widget_data));
assert(widget_data->widget->datalen > 0);
if ((void *) widget_data->info.field.cur_hist != &widget_data->info.field.history) {
unsigned char *s = widget_data->info.field.cur_hist->data;
widget_data->info.field.cpos = int_min(strlen(s), widget_data->widget->datalen - 1);
if (widget_data->info.field.cpos)
memcpy(widget_data->cdata, s, widget_data->info.field.cpos);
} else {
widget_data->info.field.cpos = 0;
}
widget_data->cdata[widget_data->info.field.cpos] = 0;
widget_data->info.field.vpos = int_max(0, widget_data->info.field.cpos - widget_data->box.width);
}