// Prototypes for history functions, part of the user interface. #ifndef FISH_HISTORY_H #define FISH_HISTORY_H // IWYU pragma: no_include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "wutil.h" // IWYU pragma: keep struct io_streams_t; class env_stack_t; class environment_t; class operation_context_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 path_list_t; enum class history_search_type_t { // Search for commands exactly matching the given string. exact, // Search for commands containing the given string. contains, // Search for commands starting with the given string. prefix, // Search for commands containing the given glob pattern. contains_glob, // Search for commands starting with the given glob pattern. prefix_glob, // Matches everything. match_everything, }; typedef uint64_t history_identifier_t; /// Ways that a history item may be written to disk (or omitted). enum class history_persistence_mode_t : uint8_t { disk, // the history item is written to disk normally memory, // the history item is stored in-memory only, not written to disk ephemeral, // the history item is stored in-memory and deleted when a new item is added }; class history_item_t { public: /// Construct from a text, timestamp, and optional identifier. /// If \p no_persist is set, then do not write this item to disk. explicit history_item_t( wcstring str = {}, time_t when = 0, history_identifier_t ident = 0, history_persistence_mode_t persist_mode = history_persistence_mode_t::disk); /// \return the text as a string. const wcstring &str() const { return contents; } /// \return whether the text is empty. bool empty() const { return contents.empty(); } // \return wehther our contents matches a search term. bool matches_search(const wcstring &term, enum history_search_type_t type, bool case_sensitive) const; /// \return the timestamp for creating this history item. time_t timestamp() const { return creation_timestamp; } /// \return whether this item should be persisted (written to disk). bool should_write_to_disk() const { return persist_mode == history_persistence_mode_t::disk; } /// Get and set the list of arguments which referred to files. /// This is used for autosuggestion hinting. const path_list_t &get_required_paths() const { return required_paths; } void set_required_paths(path_list_t paths) { required_paths = std::move(paths); } private: // Attempts to merge two compatible history items together. bool merge(const history_item_t &item); // The actual contents of the entry. wcstring contents; // Original creation time for the entry. time_t creation_timestamp; // Paths that we require to be valid for this item to be autosuggested. path_list_t required_paths; // Sometimes unique identifier used for hinting. history_identifier_t identifier; // If set, do not write this item to disk. history_persistence_mode_t persist_mode; friend class history_t; friend struct history_impl_t; friend class history_lru_cache_t; friend class history_tests_t; }; typedef std::deque history_item_list_t; struct history_impl_t; class history_t { friend class history_tests_t; struct impl_wrapper_t; const std::unique_ptr wrap_; // No copying or moving. history_t() = delete; history_t(const history_t &) = delete; history_t(history_t &&) = delete; history_t &operator=(const history_t &) = delete; history_t &operator=(history_t &&) = delete; acquired_lock impl(); acquired_lock impl() const; // Privately add an item. If pending, the item will not be returned by history searches until a // call to resolve_pending. Any trailing ephemeral items are dropped. void add(history_item_t &&item, bool pending = false); // Add a new history item with text \p str to the end of history. void add(wcstring str); public: explicit history_t(wcstring name); ~history_t(); // Whether we're in maximum chaos mode, useful for testing. // This causes things like locks to fail. static bool chaos_mode; // Whether to force the read path instead of mmap. This is useful for testing. static bool never_mmap; // Returns history with the given name, creating it if necessary. static std::shared_ptr with_name(const wcstring &name); /// Returns whether this is using the default name. bool is_default() const; // Determines whether the history is empty. Unfortunately this cannot be const, since it may // require populating the history. bool is_empty(); // Remove a history item. void remove(const wcstring &str); /// Remove any trailing ephemeral items. void remove_ephemeral_items(); // Add a new pending history item to the end, and then begin file detection on the items to // determine which arguments are paths. Arguments may be expanded (e.g. with PWD and variables) // using the given \p vars. The item has the given \p persist_mode. static void add_pending_with_file_detection( const std::shared_ptr &self, const wcstring &str, const std::shared_ptr &vars, history_persistence_mode_t persist_mode = history_persistence_mode_t::disk); // Resolves any pending history items, so that they may be returned in history searches. void resolve_pending(); // Saves history. void save(); // Searches history. bool search(history_search_type_t search_type, const wcstring_list_t &search_args, const wchar_t *show_time_format, size_t max_items, bool case_sensitive, bool null_terminate, bool reverse, const cancel_checker_t &cancel_check, io_streams_t &streams); // 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 list. This is intended for the $history environment variable. // This may be long! void get_history(wcstring_list_t &result); // Let indexes be a list of one-based indexes into the history, matching the interpretation of // $history. That is, $history[1] is the most recently executed command. Values less than one // are skipped. Return a mapping from index to history item text. std::unordered_map items_at_indexes(const std::vector &idxs); // 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.) history_item_t item_at_index(size_t idx); // Return the number of history entries. size_t size(); }; /// Flags for history searching. enum { // If set, ignore case. history_search_ignore_case = 1 << 0, // If set, do not deduplicate, which can help performance. history_search_no_dedup = 1 << 1 }; using history_search_flags_t = uint32_t; /// Support for searching a history backwards. /// Note this does NOT de-duplicate; it is the caller's responsibility to do so. class history_search_t { private: // The history in which we are searching. // TODO: this should be a shared_ptr. history_t *history_; // The original search term. wcstring orig_term_; // The (possibly lowercased) search term. wcstring canon_term_; // Our search type. enum history_search_type_t search_type_ { history_search_type_t::contains }; // Our flags. history_search_flags_t flags_{0}; // The current history item. maybe_t current_item_; // Index of the current history item. size_t current_index_{0}; // If deduping, the items we've seen. std::unordered_set deduper_; // return whether we are case insensitive. bool ignores_case() const { return flags_ & history_search_ignore_case; } // return whether we deduplicate items. bool dedup() const { return !(flags_ & history_search_no_dedup); } public: // Gets the original search term. const wcstring &original_term() const { return orig_term_; } // Finds the previous search result (backwards in time). Returns true if one was found. bool go_backwards(); // Returns the current search result item. asserts if there is no current item. const history_item_t ¤t_item() const; // Returns the current search result item contents. asserts if there is no current item. const wcstring ¤t_string() const; // Construct from a history pointer; the caller is responsible for ensuring the history stays // alive. history_search_t(history_t *hist, const wcstring &str, enum history_search_type_t type = history_search_type_t::contains, history_search_flags_t flags = 0) : history_(hist), orig_term_(str), canon_term_(str), search_type_(type), flags_(flags) { if (ignores_case()) { std::transform(canon_term_.begin(), canon_term_.end(), canon_term_.begin(), towlower); } } // Construct from a shared_ptr. TODO: this should be the only constructor. history_search_t(const std::shared_ptr &hist, const wcstring &str, enum history_search_type_t type = history_search_type_t::contains, history_search_flags_t flags = 0) : history_search_t(hist.get(), str, type, flags) {} // Default constructor. history_search_t() = default; }; /// Saves the new history to disk. void history_save_all(); /// Return the prefix for the files to be used for command and read history. wcstring history_session_id(const environment_t &vars); /// Given a list of proposed paths and a context, perform variable and home directory expansion, /// and detect if the result expands to a value which is also the path to a file. /// Wildcard expansions are suppressed - see implementation comments for why. /// This is used for autosuggestion hinting. If we add an item to history, and one of its arguments /// refers to a file, then we only want to suggest it if there is a valid file there. /// This does disk I/O and may only be called in a background thread. path_list_t expand_and_detect_paths(const path_list_t &paths, const environment_t &vars); /// Given a list of proposed paths and a context, expand each one and see if it refers to a file. /// Wildcard expansions are suppressed. /// \return true if \p paths is empty or every path is valid. bool all_paths_are_valid(const path_list_t &paths, const operation_context_t &ctx); /// Sets private mode on. Once in private mode, it cannot be turned off. void start_private_mode(env_stack_t &vars); /// Queries private mode status. bool in_private_mode(const environment_t &vars); #endif