Improve chat history (#12975)

This commit is contained in:
Jude Melton-Houghton 2023-01-14 16:14:37 -05:00 committed by GitHub
parent 8fded9d990
commit 2f9f0c0900
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 103 additions and 43 deletions

View 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;

View 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