]> git.lizzy.rs Git - minetest.git/commitdiff
Improve chat history (#12975)
authorJude Melton-Houghton <jwmhjwmh@gmail.com>
Sat, 14 Jan 2023 21:14:37 +0000 (16:14 -0500)
committerGitHub <noreply@github.com>
Sat, 14 Jan 2023 21:14:37 +0000 (16:14 -0500)
src/chat.cpp
src/chat.h

index 9483fb96e5c5f9e3f16148e05362b6a68a2921d2..b021a3e6bc9c405f4b20a92dfc908067d89b3130 100644 (file)
@@ -454,9 +454,36 @@ ChatPrompt::ChatPrompt(const std::wstring &prompt, u32 history_limit):
 {
 }
 
+const std::wstring &ChatPrompt::getLineRef() const
+{
+       return m_history_index >= m_history.size() ? m_line : m_history[m_history_index].line;
+}
+
+std::wstring &ChatPrompt::makeLineRef()
+{
+       if (m_history_index >= m_history.size()) {
+               return m_line;
+       } else {
+               if (!m_history[m_history_index].saved)
+                       m_history[m_history_index].saved = m_history[m_history_index].line;
+               return m_history[m_history_index].line;
+       }
+}
+
+bool ChatPrompt::HistoryEntry::operator==(const ChatPrompt::HistoryEntry &other)
+{
+       if (line != other.line)
+               return false;
+       if (saved == other.saved)
+               return true;
+       if ((!saved || saved == line) && (!other.saved || other.saved == other.line))
+               return true;
+       return false;
+}
+
 void ChatPrompt::input(wchar_t ch)
 {
-       m_line.insert(m_cursor, 1, ch);
+       makeLineRef().insert(m_cursor, 1, ch);
        m_cursor++;
        clampView();
        m_nick_completion_start = 0;
@@ -465,7 +492,7 @@ void ChatPrompt::input(wchar_t ch)
 
 void ChatPrompt::input(const std::wstring &str)
 {
-       m_line.insert(m_cursor, str);
+       makeLineRef().insert(m_cursor, str);
        m_cursor += str.size();
        clampView();
        m_nick_completion_start = 0;
@@ -474,22 +501,38 @@ void ChatPrompt::input(const std::wstring &str)
 
 void ChatPrompt::addToHistory(const std::wstring &line)
 {
+       std::wstring old_line = getLine();
+       if (m_history_index < m_history.size()) {
+               auto entry = m_history.begin() + m_history_index;
+               if (entry->saved && entry->line == line) {
+                       entry->line = *entry->saved;
+                       entry->saved = nullopt;
+                       // Remove potential duplicates
+                       auto dup_before = std::find(m_history.begin(), entry, *entry);
+                       if (dup_before != entry)
+                               m_history.erase(dup_before);
+                       else if (std::find(entry + 1, m_history.end(), *entry) != m_history.end())
+                               m_history.erase(entry);
+               }
+       }
        if (!line.empty() &&
-                       (m_history.size() == 0 || m_history.back() != line)) {
+                       (m_history.size() == 0 || m_history.back().line != line)) {
+               HistoryEntry entry(line);
                // Remove all duplicates
-               m_history.erase(std::remove(m_history.begin(), m_history.end(),
-                       line), m_history.end());
+               m_history.erase(std::remove(m_history.begin(), m_history.end(), entry),
+                               m_history.end());
                // Push unique line
-               m_history.push_back(line);
+               m_history.push_back(std::move(entry));
        }
        if (m_history.size() > m_history_limit)
                m_history.erase(m_history.begin());
        m_history_index = m_history.size();
+       m_line = std::move(old_line);
 }
 
 void ChatPrompt::clear()
 {
-       m_line.clear();
+       makeLineRef().clear();
        m_view = 0;
        m_cursor = 0;
        m_nick_completion_start = 0;
@@ -498,8 +541,8 @@ void ChatPrompt::clear()
 
 std::wstring ChatPrompt::replace(const std::wstring &line)
 {
-       std::wstring old_line = m_line;
-       m_line =  line;
+       std::wstring old_line = getLine();
+       makeLineRef() = line;
        m_view = m_cursor = line.size();
        clampView();
        m_nick_completion_start = 0;
@@ -509,24 +552,23 @@ std::wstring ChatPrompt::replace(const std::wstring &line)
 
 void ChatPrompt::historyPrev()
 {
-       if (m_history_index != 0)
-       {
+       if (m_history_index != 0) {
                --m_history_index;
-               replace(m_history[m_history_index]);
+               m_view = m_cursor = getLineRef().size();
+               clampView();
+               m_nick_completion_start = 0;
+               m_nick_completion_end = 0;
        }
 }
 
 void ChatPrompt::historyNext()
 {
-       if (m_history_index + 1 >= m_history.size())
-       {
-               m_history_index = m_history.size();
-               replace(L"");
-       }
-       else
-       {
-               ++m_history_index;
-               replace(m_history[m_history_index]);
+       if (m_history_index < m_history.size()) {
+               m_history_index++;
+               m_view = m_cursor = getLineRef().size();
+               clampView();
+               m_nick_completion_start = 0;
+               m_nick_completion_end = 0;
        }
 }
 
@@ -541,6 +583,7 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
        //     m_nick_completion_start..m_nick_completion_end are the
        //     interval where the originally used prefix was. Cycle
        //     through the list of completions of that prefix.
+       const std::wstring &line = getLineRef();
        u32 prefix_start = m_nick_completion_start;
        u32 prefix_end = m_nick_completion_end;
        bool initial = (prefix_end == 0);
@@ -548,14 +591,14 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
        {
                // no previous nick completion is active
                prefix_start = prefix_end = m_cursor;
-               while (prefix_start > 0 && !iswspace(m_line[prefix_start-1]))
+               while (prefix_start > 0 && !iswspace(line[prefix_start-1]))
                        --prefix_start;
-               while (prefix_end < m_line.size() && !iswspace(m_line[prefix_end]))
+               while (prefix_end < line.size() && !iswspace(line[prefix_end]))
                        ++prefix_end;
                if (prefix_start == prefix_end)
                        return;
        }
-       std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start);
+       std::wstring prefix = line.substr(prefix_start, prefix_end - prefix_start);
 
        // find all names that start with the selected prefix
        std::vector<std::wstring> completions;
@@ -576,9 +619,9 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
        u32 replacement_index = 0;
        if (!initial)
        {
-               while (word_end < m_line.size() && !iswspace(m_line[word_end]))
+               while (word_end < line.size() && !iswspace(line[word_end]))
                        ++word_end;
-               std::wstring word = m_line.substr(prefix_start, word_end - prefix_start);
+               std::wstring word = line.substr(prefix_start, word_end - prefix_start);
 
                // cycle through completions
                for (u32 i = 0; i < completions.size(); ++i)
@@ -595,12 +638,12 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
                }
        }
        std::wstring replacement = completions[replacement_index];
-       if (word_end < m_line.size() && iswspace(m_line[word_end]))
+       if (word_end < line.size() && iswspace(line[word_end]))
                ++word_end;
 
        // replace existing word with replacement word,
        // place the cursor at the end and record the completion prefix
-       m_line.replace(prefix_start, word_end - prefix_start, replacement);
+       makeLineRef().replace(prefix_start, word_end - prefix_start, replacement);
        m_cursor = prefix_start + replacement.size();
        clampView();
        m_nick_completion_start = prefix_start;
@@ -616,7 +659,7 @@ void ChatPrompt::reformat(u32 cols)
        }
        else
        {
-               s32 length = m_line.size();
+               s32 length = getLineRef().size();
                bool was_at_end = (m_view + m_cols >= length + 1);
                m_cols = cols - m_prompt.size();
                if (was_at_end)
@@ -627,7 +670,7 @@ void ChatPrompt::reformat(u32 cols)
 
 std::wstring ChatPrompt::getVisiblePortion() const
 {
-       return m_prompt + m_line.substr(m_view, m_cols);
+       return m_prompt + getLineRef().substr(m_view, m_cols);
 }
 
 s32 ChatPrompt::getVisibleCursorPosition() const
@@ -640,7 +683,8 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
        s32 old_cursor = m_cursor;
        s32 new_cursor = m_cursor;
 
-       s32 length = m_line.size();
+       const std::wstring &line = getLineRef();
+       s32 length = line.size();
        s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
 
        switch (scope) {
@@ -650,17 +694,17 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
        case CURSOROP_SCOPE_WORD:
                if (dir == CURSOROP_DIR_RIGHT) {
                        // skip one word to the right
-                       while (new_cursor < length && iswspace(m_line[new_cursor]))
+                       while (new_cursor < length && iswspace(line[new_cursor]))
                                new_cursor++;
-                       while (new_cursor < length && !iswspace(m_line[new_cursor]))
+                       while (new_cursor < length && !iswspace(line[new_cursor]))
                                new_cursor++;
-                       while (new_cursor < length && iswspace(m_line[new_cursor]))
+                       while (new_cursor < length && iswspace(line[new_cursor]))
                                new_cursor++;
                } else {
                        // skip one word to the left
-                       while (new_cursor >= 1 && iswspace(m_line[new_cursor - 1]))
+                       while (new_cursor >= 1 && iswspace(line[new_cursor - 1]))
                                new_cursor--;
-                       while (new_cursor >= 1 && !iswspace(m_line[new_cursor - 1]))
+                       while (new_cursor >= 1 && !iswspace(line[new_cursor - 1]))
                                new_cursor--;
                }
                break;
@@ -680,10 +724,10 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
                break;
        case CURSOROP_DELETE:
                if (m_cursor_len > 0) { // Delete selected text first
-                       m_line.erase(m_cursor, m_cursor_len);
+                       makeLineRef().erase(m_cursor, m_cursor_len);
                } else {
                        m_cursor = MYMIN(new_cursor, old_cursor);
-                       m_line.erase(m_cursor, abs(new_cursor - old_cursor));
+                       makeLineRef().erase(m_cursor, abs(new_cursor - old_cursor));
                }
                m_cursor_len = 0;
                break;
@@ -707,7 +751,7 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
 
 void ChatPrompt::clampView()
 {
-       s32 length = m_line.size();
+       s32 length = getLineRef().size();
        if (length + 1 <= m_cols)
        {
                m_view = 0;
index fc080f64b9c87a0b9784dceb75dc3ec97abe9596..280701d47d9dd39b81474ff6496619e055c3558d 100644 (file)
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "irrlichttypes.h"
 #include "util/enriched_string.h"
+#include "util/Optional.h"
 #include "settings.h"
 
 // Chat console related classes
@@ -172,10 +173,10 @@ class ChatPrompt
        void addToHistory(const std::wstring &line);
 
        // Get current line
-       std::wstring getLine() const { return m_line; }
+       std::wstring getLine() const { return getLineRef(); }
 
        // Get section of line that is currently selected
-       std::wstring getSelection() const { return m_line.substr(m_cursor, m_cursor_len); }
+       std::wstring getSelection() const { return getLineRef().substr(m_cursor, m_cursor_len); }
 
        // Clear the current line
        void clear();
@@ -233,18 +234,33 @@ class ChatPrompt
        void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);
 
 protected:
+       const std::wstring &getLineRef() const;
+
+       std::wstring &makeLineRef();
+
        // set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols
        // if line can be fully shown, set m_view to zero
        // else, also ensure m_view <= m_line.size() + 1 - m_cols
        void clampView();
 
 private:
+       struct HistoryEntry {
+               std::wstring line;
+               // If line is edited, saved holds the unedited version.
+               Optional<std::wstring> saved;
+
+               HistoryEntry(const std::wstring &line): line(line) {}
+
+               bool operator==(const HistoryEntry &other);
+               bool operator!=(const HistoryEntry &other) { return !(*this == other); }
+       };
+
        // Prompt prefix
        std::wstring m_prompt = L"";
-       // Currently edited line
+       // Non-historical edited line
        std::wstring m_line = L"";
        // History buffer
-       std::vector<std::wstring> m_history;
+       std::vector<HistoryEntry> m_history;
        // History index (0 <= m_history_index <= m_history.size())
        u32 m_history_index = 0;
        // Maximum number of history entries