{
}
+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;
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;
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;
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;
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;
}
}
// 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);
{
// 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;
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)
}
}
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;
}
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)
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
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) {
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;
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;
void ChatPrompt::clampView()
{
- s32 length = m_line.size();
+ s32 length = getLineRef().size();
if (length + 1 <= m_cols)
{
m_view = 0;
#include "irrlichttypes.h"
#include "util/enriched_string.h"
+#include "util/Optional.h"
#include "settings.h"
// Chat console related classes
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();
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