From 1bca7696286baa9abf650591e9fd1329433ebb7a Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:36:41 -0400 Subject: [PATCH] Allow for temporary editing of history lines This reworks history handling to allow editing of temporary copies of history lines when the user uses up arrow or down arrow to navigate to them. --- linenoise.hpp | 94 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/linenoise.hpp b/linenoise.hpp index dbf3365..efbee16 100644 --- a/linenoise.hpp +++ b/linenoise.hpp @@ -151,6 +151,7 @@ #include #include #include +#include #include #include #include @@ -177,6 +178,7 @@ class linenoiseState { void DisableMultiLine(); bool AddHistory(const char *line); + bool AddHistory(std::string &line); bool LoadHistory(const char *path); bool SaveHistory(const char *path); const std::vector &GetHistory() { return history_; }; @@ -225,15 +227,15 @@ class linenoiseState { int len_ = 0; /* Current edited line length. */ int cols_ = -1; /* Number of columns in terminal. */ int maxrows_ = 0; /* Maximum num of rows used so far (multiline mode) */ - int history_index_ = 0; /* The history index we are currently editing. */ + long int history_index_ = -INT_MAX; /* The history index we are currently editing. */ char wbuf_[LINENOISE_MAX_LINE] = {'\0'}; - std::string history_tmpbuf_; bool mlmode_ = false; /* Multi line mode. Default is single line. */ std::mutex mutex_; std::string prompt_ = std::string("> "); /* Prompt to display. */ size_t history_max_len_ = LINENOISE_DEFAULT_HISTORY_MAX_LEN; std::vector history_; + std::map history_tmpbufs_; }; @@ -2118,24 +2120,45 @@ inline void linenoiseState::EditMoveEnd() { #define LINENOISE_HISTORY_NEXT 0 #define LINENOISE_HISTORY_PREV 1 inline void linenoiseState::EditHistoryNext(int dir) { - if (history_.size() > 1) { - /* Update the current history entry before to - * overwrite it with the next one. */ - history_[history_.size() - 1 - history_index_] = buf_; - /* Show the new entry */ - history_index_ += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (history_index_ < 0) { - history_index_ = 0; - return; - } else if (history_index_ >= (int)history_.size()) { - history_index_ = static_cast(history_.size())-1; - return; - } - memset(buf_, 0, buf_len_); - strcpy(buf_,history_[history_.size() - 1 - history_index_].c_str()); - len_ = pos_ = static_cast(strlen(buf_)); - RefreshLine(); - } + long int index_cnt = static_cast(history_.size()); + // If we have no history, there's nothing to do + if (!index_cnt) + return; + + // Whatever index we're on, stash a working copy of the string + history_tmpbufs_[history_index_] = std::string(buf_); + + // Now that we've stored the current state, adjust our index + history_index_ += (dir == LINENOISE_HISTORY_PREV) ? -1 : 1; + + // If we've gone down past the last history entry, circle back + // around to the current working line + if (history_index_ < 0) + history_index_ = index_cnt; + + // If we went up past the working line, circle back to the first + // history entry + if (history_index_ > index_cnt) + history_index_ = 0; + + // Now we have the new history_index, so make sure this one also + // has a temporary buffer + if (history_tmpbufs_.find(history_index_) == history_tmpbufs_.end()) { + if (history_index_ == index_cnt) { + // buf_ holds the current line + history_tmpbufs_[history_index_] = std::string(buf_); + } else { + // For older historical lines, make a temporary copy for modding + history_tmpbufs_[history_index_] = history_[history_index_]; + } + } + + // Put the currently selected history line's temporary + // string contents into the buffer + memset(buf_, 0, buf_len_); + strcpy(buf_, history_tmpbufs_[history_index_].c_str()); + len_ = pos_ = static_cast(strlen(buf_)); + RefreshLine(); } /* Delete the character at the right of the cursor without altering the cursor @@ -2188,10 +2211,6 @@ inline void linenoiseState::EditDeletePrevWord() { * The function returns the length of the current buffer. */ inline int linenoiseState::Edit() { - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - AddHistory(""); - if (write(ofd_, prompt_.c_str(), static_cast(prompt_.length())) == -1) return -1; while(1) { int c; @@ -2222,7 +2241,6 @@ inline int linenoiseState::Edit() switch(c) { case ENTER: /* enter */ - if (!history_.empty()) history_.pop_back(); if (mlmode_) EditMoveEnd(); return (int)len_; case CTRL_C: /* ctrl-c */ @@ -2458,21 +2476,35 @@ inline void linenoiseAtExit(void) { * histories, but will work well for a few hundred of entries. * * Using a circular buffer is smarter, but a bit more complex to handle. */ -inline bool linenoiseState::AddHistory(const char* line) { +inline bool linenoiseState::AddHistory(std::string &line) { + // Clear the temporary copies + history_tmpbufs_.clear(); + history_index_ = static_cast(history_.size()); if (history_max_len_ == 0) return false; + if (!line.length()) return false; - /* Don't add duplicated lines. */ - if (!history_.empty() && history_.back() == line) return false; + /* Don't add duplicate lines. */ + if (history_.size() && history_[history_.size() - 1] == line) + return false; /* If we reached the max length, remove the older line. */ - if (history_.size() == history_max_len_) { - history_.erase(history_.begin()); - } + if (history_.size() == history_max_len_) + history_.pop_back(); + + /* Checks passed - add the line */ history_.push_back(line); + /* Update the index to the new "working" line value */ + history_index_ = static_cast(history_.size()); + return true; } +inline bool linenoiseState::AddHistory(const char *line) { + std::string l(line); + return AddHistory(l); +} + /* Set the maximum length for the history. This function can be called even * if there is already some history, the function will make sure to retain * just the latest 'len' elements if the new history length value is smaller