/* vifm * Copyright (C) 2016 xaizek. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "flist_hist.h" #include <string.h> /* memmove() */ #include "cfg/config.h" #include "compat/reallocarray.h" #include "ui/fileview.h" #include "ui/ui.h" #include "utils/fs.h" #include "utils/macros.h" #include "utils/path.h" #include "utils/str.h" #include "filelist.h" #include "flist_pos.h" static void navigate_to_history_pos(view_t *view, int pos); static void free_view_history(view_t *view); static void reduce_view_history(view_t *view, int new_size); static void free_view_history_items(const history_t history[], size_t len); static int find_in_hist(const view_t *view, const view_t *source, int *pos, int *rel_pos); static history_t * find_hist_entry(const view_t *view, const char dir[]); void flist_hist_go_back(view_t *view) { /* When in custom view, we don't want to skip top history item. */ int pos = flist_custom_active(view) ? view->history_pos : (view->history_pos - 1); while(pos >= 0) { const char *const dir = view->history[pos].dir; if(is_valid_dir(dir) && !paths_are_equal(view->curr_dir, dir)) { break; } --pos; } if(pos >= 0) { navigate_to_history_pos(view, pos); } } void flist_hist_go_forward(view_t *view) { int pos = view->history_pos + 1; while(pos <= view->history_num - 1) { const char *const dir = view->history[pos].dir; if(is_valid_dir(dir) && !paths_are_equal(view->curr_dir, dir)) { break; } ++pos; } if(pos <= view->history_num - 1) { navigate_to_history_pos(view, pos); } } /* Changes current directory of the view to one of previously visited * locations. */ static void navigate_to_history_pos(view_t *view, int pos) { curr_stats.drop_new_dir_hist = 1; if(change_directory(view, view->history[pos].dir) < 0) { curr_stats.drop_new_dir_hist = 0; return; } curr_stats.drop_new_dir_hist = 0; load_dir_list(view, 0); fpos_set_pos(view, fpos_find_by_name(view, view->history[pos].file)); view->history_pos = pos; } void flist_hist_resize(view_t *view, int new_size) { const int old_size = MAX(cfg.history_len, 0); const int delta = new_size - old_size; if(new_size <= 0) { free_view_history(view); return; } if(delta < 0) { reduce_view_history(view, new_size); } if(delta > 0 && view->history_num >= new_size) { /* We must be restarting, don't truncate history. */ return; } view->history = reallocarray(view->history, new_size, sizeof(history_t)); if(delta > 0) { int real_delta = new_size - MAX(view->history_num, old_size); if(real_delta > 0) { size_t hist_item_len = sizeof(history_t)*real_delta; memset(view->history + view->history_num, 0, hist_item_len); } } } /* Clears and frees directory history of the view. */ static void free_view_history(view_t *view) { free_view_history_items(view->history, view->history_num); free(view->history); view->history = NULL; view->history_num = 0; view->history_pos = 0; } /* Moves items of directory history when size of history becomes smaller. */ static void reduce_view_history(view_t *view, int new_size) { const int delta = MIN(view->history_num - new_size, view->history_pos); if(delta <= 0) { return; } free_view_history_items(view->history, delta); memmove(view->history, view->history + delta, sizeof(history_t)*(view->history_num - delta)); if(view->history_num > new_size) { view->history_num = new_size; } view->history_pos -= delta; } void flist_hist_save(view_t *view) { flist_hist_setup(view, NULL, NULL, -1, -1); } void flist_hist_setup(view_t *view, const char path[], const char file[], int rel_pos, time_t timestamp) { int x; /* This could happen on FUSE error. */ if(view->list_rows <= 0 && file == NULL) return; if(cfg.history_len <= 0) return; if(flist_custom_active(view)) return; if(path == NULL) path = view->curr_dir; if(file == NULL) file = get_current_entry(view)->name; if(rel_pos < 0) rel_pos = view->list_pos - view->top_line; if(view->history_num > 0 && stroscmp(view->history[view->history_pos].dir, path) == 0) { if(curr_stats.load_stage < 2 || file[0] == '\0') return; x = view->history_pos; (void)replace_string(&view->history[x].file, file); view->history[x].rel_pos = rel_pos; return; } if(curr_stats.drop_new_dir_hist) { return; } if(view->history_num > 0 && view->history_pos != view->history_num - 1) { x = view->history_num - 1; while(x > view->history_pos) { free_view_history_items(&view->history[x--], 1); } view->history_num = view->history_pos + 1; } x = view->history_num; /* Directory history can exceed cfg.history_len during restart. */ if(x >= cfg.history_len) { int surplus = x - cfg.history_len + 1; free_view_history_items(view->history, surplus); memmove(view->history, view->history + surplus, sizeof(history_t)*(cfg.history_len - 1)); x = cfg.history_len - 1; view->history_num = x; } view->history[x].dir = strdup(path); view->history[x].file = strdup(file); view->history[x].timestamp = timestamp; view->history[x].rel_pos = rel_pos; ++view->history_num; view->history_pos = view->history_num - 1; } /* Frees memory previously allocated for specified history items. */ static void free_view_history_items(const history_t history[], size_t len) { size_t i; for(i = 0; i < len; ++i) { free(history[i].dir); free(history[i].file); } } void flist_hist_clear(view_t *view) { int i; for(i = 0; i <= view->history_pos && i < view->history_num; ++i) { view->history[i].file[0] = '\0'; } } void flist_hist_lookup(view_t *view, const view_t *source) { int pos = 0; int rel_pos = -1; if(cfg.history_len > 0 && source->history_num > 0 && curr_stats.ch_pos) { if(!find_in_hist(view, source, &pos, &rel_pos)) { view->list_pos = 0; view->curr_line = 0; view->top_line = 0; return; } } if(pos < 0) pos = 0; view->list_pos = pos; if(rel_pos >= 0) { view->top_line = pos - MIN(view->window_cells - 1, rel_pos); if(view->top_line < 0) view->top_line = 0; view->curr_line = pos - view->top_line; } else { const int last = fpos_get_last_visible_cell(view); if(view->list_pos < view->window_cells) { fview_scroll_back_by(view, view->top_line); } else if(view->list_pos > last) { fview_scroll_fwd_by(view, view->list_pos - last); } } (void)fview_enforce_scroll_offset(view); } int flist_hist_find(const view_t *view, entries_t entries, const char dir[], int *top) { int pos; const history_t *const hist_entry = find_hist_entry(view, dir); if(hist_entry == NULL) { *top = 0; return 0; } for(pos = 0; pos < entries.nentries; ++pos) { if(stroscmp(entries.entries[pos].name, hist_entry->file) == 0) { break; } } if(pos >= entries.nentries) { pos = 0; } *top = pos - MIN(entries.nentries, hist_entry->rel_pos); if(*top < 0) { *top = 0; } return pos; } /* Searches for current directory of the view in the history of source. Returns * non-zero if something was found, otherwise zero is returned. On success, * *pos and *rel_pos are set, but might be negative if they aren't valid when * applied to existing list of files. */ static int find_in_hist(const view_t *view, const view_t *source, int *pos, int *rel_pos) { const history_t *const hist_entry = find_hist_entry(source, view->curr_dir); if(hist_entry != NULL) { *pos = fpos_find_by_name(view, hist_entry->file); *rel_pos = hist_entry->rel_pos; return 1; } if(view->last_dir != NULL && path_starts_with(view->last_dir, view->curr_dir) && stroscmp(view->last_dir, view->curr_dir) != 0 && strchr(view->last_dir + strlen(view->curr_dir) + 1, '/') == NULL) { /* This handles positioning of cursor on directory we just left by doing * `cd ..` or equivalent. */ const char *const dir_name = view->last_dir + strlen(view->curr_dir) + 1U; *pos = fpos_find_by_name(view, dir_name); *rel_pos = -1; return 1; } return 0; } void flist_hist_update(view_t *view, const char dir[], const char file[], int rel_pos) { history_t *const hist_entry = find_hist_entry(view, dir); if(hist_entry != NULL) { (void)replace_string(&hist_entry->file, file); hist_entry->rel_pos = rel_pos; } } /* Finds entry in view history by directory path. Returns pointer to the entry * or NULL. */ static history_t * find_hist_entry(const view_t *view, const char dir[]) { history_t *const history = view->history; int i = view->history_pos; if(view->history_num <= 0) { return NULL; } if(stroscmp(history[i].dir, dir) == 0 && history[i].file[0] == '\0') { --i; } for(; i >= 0 && history[i].dir[0] != '\0'; --i) { if(stroscmp(history[i].dir, dir) == 0) { return &history[i]; } } return NULL; } void flist_hist_clone(view_t *dst, const view_t *src) { int i; free_view_history_items(dst->history, dst->history_num); dst->history_pos = 0; dst->history_num = 0; for(i = 0; i < src->history_num; ++i) { const history_t *const hist_entry = &src->history[i]; flist_hist_setup(dst, hist_entry->dir, hist_entry->file, hist_entry->rel_pos, hist_entry->timestamp); } dst->history_pos = MIN(src->history_pos, dst->history_num - 1); } /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set cinoptions+=t0 : */