fish-shell/src/history.h

364 lines
13 KiB
C
Raw Normal View History

// Prototypes for history functions, part of the user interface.
#ifndef FISH_HISTORY_H
#define FISH_HISTORY_H
// IWYU pragma: no_include <cstddef>
2015-07-25 23:14:25 +08:00
#include <pthread.h>
#include <stddef.h>
2015-07-25 23:14:25 +08:00
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <wctype.h>
#include <deque>
#include <memory>
Squashed commit of the following: commit 50f414a45d58fcab664ff662dd27befcfa0fdd95 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:35 2017 -0500 Converted file_id_t set to unordered_set with custom hash commit 83ef2dd7cc1bc3e4fdf0b2d3546d6811326cc3c9 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:43:14 2017 -0500 Converted remaining set<wcstring> to unordered_set<wcstring> commit 053da88f933f27505b3cf4810402e2a2be070203 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:29:21 2017 -0500 Switched function sets to unordered_set commit d469742a14ac99599022a9258cda8255178826b5 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:21:32 2017 -0500 Converted list of modified variables to an unordered set commit 5c06f866beeafb23878b1a932c7cd2558412c283 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 13:15:20 2017 -0500 Convert const_string_set_t to std::unordered_set As it is a readonly-list of raw character pointer strings (not wcstring), this necessitated the addition of a hashing function since the C++ standard library does not come with a char pointer hash function. To that end, a zlib-licensed [0] port of the excellent, lightweight XXHash family of 32- and 64-bit hashing algorithms in the form of a C++ header-only include library has been included. XXHash32/64 is pretty much universally the fastest hashing library for general purpose applications, and has been thoroughly vetted and is used in countless open source projects. The single-header version of this library makes it a lot simpler to include in the fish project, and the license compatibility with fish' GPLv2 and the zero-lib nature should make it an easy decision. std::unordered_set brings a massive speedup as compared to the default std::set, and the further use of the fast XXHash library to provide the string hashing should make all forms of string lookups in fish significantly faster (to a user-noticeable extent). 0: http://create.stephan-brumme.com/about.html commit 30d7710be8f0c23a4d42f7e713fcb7850f99036e Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:29:39 2017 -0500 Using std::unordered_set for completions backing store While the completions shown to the user are sorted, their storage in memory does not need to be since they are re-sorted before they are shown in completions.cpp. commit 695e83331d7a60ba188e57f6ea0d9b6da54860c6 Author: Mahmoud Al-Qudsi <mqudsi@neosmart.net> Date: Sat Aug 19 12:06:53 2017 -0500 Updated is_loading to use unordered_set
2017-08-20 04:29:52 +08:00
#include <unordered_set>
2015-07-25 23:14:25 +08:00
#include <string>
#include <utility>
#include <vector>
#include "common.h"
#include "wutil.h" // IWYU pragma: keep
2012-02-06 08:42:24 +08:00
struct io_streams_t;
// Fish supports multiple shells writing to history at once. Here is its strategy:
//
// 1. All history files are append-only. Data, once written, is never modified.
//
// 2. A history file may be re-written ("vacuumed"). This involves reading in the file and writing a
// new one, while performing maintenance tasks: discarding items in an LRU fashion until we reach
// the desired maximum count, removing duplicates, and sorting them by timestamp (eventually, not
// implemented yet). The new file is atomically moved into place via rename().
//
// 3. History files are mapped in via mmap(). Before the file is mapped, the file takes a fcntl read
// lock. The purpose of this lock is to avoid seeing a transient state where partial data has been
// written to the file.
//
// 4. History is appended to under a fcntl write lock.
//
// 5. The chaos_mode boolean can be set to true to do things like lower buffer sizes which can
// trigger race conditions. This is useful for testing.
typedef std::vector<wcstring> path_list_t;
enum history_search_type_t {
// Search for commands exactly matching the given string.
HISTORY_SEARCH_TYPE_EXACT = 1,
// Search for commands containing the given string.
2012-02-07 02:52:13 +08:00
HISTORY_SEARCH_TYPE_CONTAINS,
// Search for commands starting with the given string.
2012-02-07 02:52:13 +08:00
HISTORY_SEARCH_TYPE_PREFIX
};
typedef uint32_t history_identifier_t;
class history_item_t {
2012-02-06 08:42:24 +08:00
friend class history_t;
friend class history_tests_t;
2012-02-06 08:42:24 +08:00
private:
// Attempts to merge two compatible history items together.
bool merge(const history_item_t &item);
2012-02-06 08:42:24 +08:00
// The actual contents of the entry.
wcstring contents; // value as entered by the user
wcstring contents_lower; // value normalized to all lowercase for case insensitive comparisons
// Original creation time for the entry.
time_t creation_timestamp;
// Sometimes unique identifier used for hinting.
history_identifier_t identifier;
// Paths that we require to be valid for this item to be autosuggested.
path_list_t required_paths;
public:
explicit history_item_t(const wcstring &str, time_t when = 0, history_identifier_t ident = 0);
2016-05-06 06:09:31 +08:00
const wcstring &str() const { return contents; }
const wcstring &str_lower() const { return contents_lower; }
bool empty() const { return contents.empty(); }
// Whether our contents matches a search term.
bool matches_search(const wcstring &term, enum history_search_type_t type,
bool case_sensitive) const;
time_t timestamp() const { return creation_timestamp; }
const path_list_t &get_required_paths() const { return required_paths; }
2017-05-05 13:42:42 +08:00
void set_required_paths(const path_list_t &paths) { required_paths = paths; }
bool operator==(const history_item_t &other) const {
return contents == other.contents && creation_timestamp == other.creation_timestamp &&
required_paths == other.required_paths;
}
};
2012-06-05 12:24:42 +08:00
typedef std::deque<history_item_t> history_item_list_t;
// The type of file that we mmap'd.
enum history_file_type_t { history_type_unknown, history_type_fish_2_0, history_type_fish_1_x };
2012-02-06 08:42:24 +08:00
class history_t {
friend class history_tests_t;
2012-02-06 08:42:24 +08:00
private:
// No copying.
history_t(const history_t &);
history_t &operator=(const history_t &);
// Privately add an item. If pending, the item will not be returned by history searches until a
// call to resolve_pending.
void add(const history_item_t &item, bool pending = false);
// Lock for thread safety.
std::mutex lock;
// Internal function.
void clear_file_state();
2012-02-06 08:42:24 +08:00
// The name of this list. Used for picking a suitable filename and for switching modes.
const wcstring name;
// New items. Note that these are NOT discarded on save. We need to keep these around so we can
// distinguish between items in our history and items in the history of other shells that were
// started after we were started.
history_item_list_t new_items;
// The index of the first new item that we have not yet written.
size_t first_unwritten_new_item_index;
2014-04-01 01:01:39 +08:00
// Whether we have a pending item. If so, the most recently added item is ignored by
// item_at_index.
bool has_pending_item;
// Whether we should disable saving to the file for a time.
uint32_t disable_automatic_save_counter;
// Deleted item contents.
std::unordered_set<wcstring, wcstring_hash> deleted_items;
2012-06-05 12:24:42 +08:00
// The mmaped region for the history file.
const char *mmap_start;
2012-02-06 08:42:24 +08:00
// The size of the mmap'd region.
size_t mmap_length;
2012-02-06 08:42:24 +08:00
// The type of file we mmap'd.
history_file_type_t mmap_type;
// The file ID of the file we mmap'd.
file_id_t mmap_file_id;
// The boundary timestamp distinguishes old items from new items. Items whose timestamps are <=
// the boundary are considered "old". Items whose timestemps are > the boundary are new, and are
// ignored by this instance (unless they came from this instance). The timestamp may be adjusted
// by incorporate_external_changes().
time_t boundary_timestamp;
// How many items we add until the next vacuum. Initially a random value.
int countdown_to_vacuum;
// Figure out the offsets of our mmap data.
2012-02-06 08:42:24 +08:00
void populate_from_mmap(void);
// List of old items, as offsets into out mmap data.
std::deque<size_t> old_item_offsets;
// Whether we've loaded old items.
2012-02-06 08:42:24 +08:00
bool loaded_old;
// Loads old if necessary.
bool load_old_if_needed(void);
// Memory maps the history file if necessary.
bool mmap_if_needed(void);
// Deletes duplicates in new_items.
void compact_new_items();
// Attempts to rewrite the existing file to a target temporary file
// Returns false on error, true on success
bool rewrite_to_temporary_file(int existing_fd, int dst_fd) const;
// Saves history by rewriting the file.
bool save_internal_via_rewrite();
// Saves history by appending to the file.
bool save_internal_via_appending();
// Saves history.
void save_internal(bool vacuum);
2014-04-01 01:01:39 +08:00
// Saves history unless doing so is disabled.
void save_internal_unless_disabled();
// Do a private, read-only map of the entirety of a history file with the given name. Returns
// true if successful. Returns the mapped memory region by reference.
2017-02-08 13:52:35 +08:00
bool map_file(const wcstring &name, const char **out_map_start, size_t *out_map_len,
file_id_t *file_id) const;
// Like map_file but takes a file descriptor
bool map_fd(int fd, const char **out_map_start, size_t *out_map_len) const;
// Whether we're in maximum chaos mode, useful for testing.
bool chaos_mode;
public:
explicit history_t(const wcstring &); // constructor
// Returns history with the given name, creating it if necessary.
static history_t &history_with_name(const wcstring &name);
// Determines whether the history is empty. Unfortunately this cannot be const, since it may
// require populating the history.
bool is_empty(void);
// Add a new history item to the end. If pending is set, the item will not be returned by
// item_at_index until a call to resolve_pending(). Pending items are tracked with an offset
// into the array of new items, so adding a non-pending item has the effect of resolving all
// pending items.
void add(const wcstring &str, history_identifier_t ident = 0, bool pending = false);
2012-06-05 12:24:42 +08:00
// Remove a history item.
2012-06-05 12:24:42 +08:00
void remove(const wcstring &str);
// Add a new pending history item to the end, and then begin file detection on the items to
// determine which arguments are paths
void add_pending_with_file_detection(const wcstring &str);
// Resolves any pending history items, so that they may be returned in history searches.
void resolve_pending();
// Saves history.
2012-02-06 08:42:24 +08:00
void save();
2014-04-01 01:01:39 +08:00
// Searches history.
bool search(history_search_type_t search_type, wcstring_list_t search_args,
const wchar_t *show_time_format, long max_items, bool case_sensitive,
bool null_terminate, io_streams_t &streams);
// Enable / disable automatic saving. Main thread only!
void disable_automatic_saving();
void enable_automatic_saving();
// Irreversibly clears history.
void clear();
// Populates from older location ()in config path, rather than data path).
void populate_from_config_path();
// Populates from a bash history file.
void populate_from_bash(FILE *f);
// Incorporates the history of other shells into this history.
void incorporate_external_changes();
// Gets all the history into a string. This is intended for the $history environment variable.
// This may be long!
void get_string_representation(wcstring *result, const wcstring &separator);
2014-04-01 01:01:39 +08:00
// Sets the valid file paths for the history item with the given identifier.
void set_valid_file_paths(const wcstring_list_t &valid_file_paths, history_identifier_t ident);
// Return the specified history at the specified index. 0 is the index of the current
// commandline. (So the most recent item is at index 1.)
2012-02-06 08:42:24 +08:00
history_item_t item_at_index(size_t idx);
};
class history_search_t {
private:
// The history in which we are searching.
history_t *history;
// The search term.
wcstring term;
// Our search type.
2012-02-07 02:52:13 +08:00
enum history_search_type_t search_type;
bool case_sensitive;
// Our list of previous matches as index, value. The end is the current match.
typedef std::pair<size_t, history_item_t> prev_match_t;
std::vector<prev_match_t> prev_matches;
2012-02-06 14:30:42 +08:00
// Returns yes if a given term is in prev_matches.
2012-02-06 14:30:42 +08:00
bool match_already_made(const wcstring &match) const;
2012-02-06 08:42:24 +08:00
// Additional strings to skip (sorted).
wcstring_list_t external_skips;
bool should_skip_match(const wcstring &str) const;
public:
// Gets the search term.
const wcstring &get_term() const { return term; }
2012-02-06 08:42:24 +08:00
// Sets additional string matches to skip.
void skip_matches(const wcstring_list_t &skips);
// Finds the next search term (forwards in time). Returns true if one was found.
2012-02-06 08:42:24 +08:00
bool go_forwards(void);
// Finds the previous search result (backwards in time). Returns true if one was found.
2012-02-06 08:42:24 +08:00
bool go_backwards(void);
// Goes to the end (forwards).
2012-02-06 08:42:24 +08:00
void go_to_end(void);
// Returns if we are at the end. We start out at the end.
bool is_at_end(void) const;
// Goes to the beginning (backwards).
2012-02-06 08:42:24 +08:00
void go_to_beginning(void);
// Returns the current search result item. asserts if there is no current item.
history_item_t current_item(void) const;
// Returns the current search result item contents. asserts if there is no current item.
wcstring current_string(void) const;
// Constructor.
history_search_t(history_t &hist, const wcstring &str,
enum history_search_type_t type = HISTORY_SEARCH_TYPE_CONTAINS,
bool case_sensitive = true)
: history(&hist), term(str), search_type(type), case_sensitive(case_sensitive) {
if (!case_sensitive) {
term = wcstring();
for (wcstring::const_iterator it = str.begin(); it != str.end(); ++it) {
term.push_back(towlower(*it));
}
}
}
2012-02-06 08:42:24 +08:00
// Default constructor.
2016-12-04 12:12:53 +08:00
history_search_t()
: history(), term(), search_type(HISTORY_SEARCH_TYPE_CONTAINS), case_sensitive(true) {}
2012-02-06 08:42:24 +08:00
};
/// Init history library. The history file won't actually be loaded until the first time a history
/// search is performed.
void history_init();
/// Saves the new history to disk.
void history_destroy();
/// Perform sanity checks.
void history_sanity_check();
/// Return the prefix for the files to be used for command and read history.
wcstring history_session_id();
/// Given a list of paths and a working directory, return the paths that are valid
/// This does disk I/O and may only be called in a background thread
path_list_t valid_paths(const path_list_t &paths, const wcstring &working_directory);
/// Given a list of paths and a working directory,
/// return true if all paths in the list are valid
/// Returns true for if paths is empty
bool all_paths_are_valid(const path_list_t &paths, const wcstring &working_directory);
#endif