forked from Mirrorlandia_minetest/minetest
Improve chat history (#12975)
This commit is contained in:
parent
8fded9d990
commit
2f9f0c0900
122
src/chat.cpp
122
src/chat.cpp
@ -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)
|
void ChatPrompt::input(wchar_t ch)
|
||||||
{
|
{
|
||||||
m_line.insert(m_cursor, 1, ch);
|
makeLineRef().insert(m_cursor, 1, ch);
|
||||||
m_cursor++;
|
m_cursor++;
|
||||||
clampView();
|
clampView();
|
||||||
m_nick_completion_start = 0;
|
m_nick_completion_start = 0;
|
||||||
@ -465,7 +492,7 @@ void ChatPrompt::input(wchar_t ch)
|
|||||||
|
|
||||||
void ChatPrompt::input(const std::wstring &str)
|
void ChatPrompt::input(const std::wstring &str)
|
||||||
{
|
{
|
||||||
m_line.insert(m_cursor, str);
|
makeLineRef().insert(m_cursor, str);
|
||||||
m_cursor += str.size();
|
m_cursor += str.size();
|
||||||
clampView();
|
clampView();
|
||||||
m_nick_completion_start = 0;
|
m_nick_completion_start = 0;
|
||||||
@ -474,22 +501,38 @@ void ChatPrompt::input(const std::wstring &str)
|
|||||||
|
|
||||||
void ChatPrompt::addToHistory(const std::wstring &line)
|
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() &&
|
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
|
// Remove all duplicates
|
||||||
m_history.erase(std::remove(m_history.begin(), m_history.end(),
|
m_history.erase(std::remove(m_history.begin(), m_history.end(), entry),
|
||||||
line), m_history.end());
|
m_history.end());
|
||||||
// Push unique line
|
// Push unique line
|
||||||
m_history.push_back(line);
|
m_history.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
if (m_history.size() > m_history_limit)
|
if (m_history.size() > m_history_limit)
|
||||||
m_history.erase(m_history.begin());
|
m_history.erase(m_history.begin());
|
||||||
m_history_index = m_history.size();
|
m_history_index = m_history.size();
|
||||||
|
m_line = std::move(old_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatPrompt::clear()
|
void ChatPrompt::clear()
|
||||||
{
|
{
|
||||||
m_line.clear();
|
makeLineRef().clear();
|
||||||
m_view = 0;
|
m_view = 0;
|
||||||
m_cursor = 0;
|
m_cursor = 0;
|
||||||
m_nick_completion_start = 0;
|
m_nick_completion_start = 0;
|
||||||
@ -498,8 +541,8 @@ void ChatPrompt::clear()
|
|||||||
|
|
||||||
std::wstring ChatPrompt::replace(const std::wstring &line)
|
std::wstring ChatPrompt::replace(const std::wstring &line)
|
||||||
{
|
{
|
||||||
std::wstring old_line = m_line;
|
std::wstring old_line = getLine();
|
||||||
m_line = line;
|
makeLineRef() = line;
|
||||||
m_view = m_cursor = line.size();
|
m_view = m_cursor = line.size();
|
||||||
clampView();
|
clampView();
|
||||||
m_nick_completion_start = 0;
|
m_nick_completion_start = 0;
|
||||||
@ -509,24 +552,23 @@ std::wstring ChatPrompt::replace(const std::wstring &line)
|
|||||||
|
|
||||||
void ChatPrompt::historyPrev()
|
void ChatPrompt::historyPrev()
|
||||||
{
|
{
|
||||||
if (m_history_index != 0)
|
if (m_history_index != 0) {
|
||||||
{
|
|
||||||
--m_history_index;
|
--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()
|
void ChatPrompt::historyNext()
|
||||||
{
|
{
|
||||||
if (m_history_index + 1 >= m_history.size())
|
if (m_history_index < m_history.size()) {
|
||||||
{
|
m_history_index++;
|
||||||
m_history_index = m_history.size();
|
m_view = m_cursor = getLineRef().size();
|
||||||
replace(L"");
|
clampView();
|
||||||
}
|
m_nick_completion_start = 0;
|
||||||
else
|
m_nick_completion_end = 0;
|
||||||
{
|
|
||||||
++m_history_index;
|
|
||||||
replace(m_history[m_history_index]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
// m_nick_completion_start..m_nick_completion_end are the
|
||||||
// interval where the originally used prefix was. Cycle
|
// interval where the originally used prefix was. Cycle
|
||||||
// through the list of completions of that prefix.
|
// through the list of completions of that prefix.
|
||||||
|
const std::wstring &line = getLineRef();
|
||||||
u32 prefix_start = m_nick_completion_start;
|
u32 prefix_start = m_nick_completion_start;
|
||||||
u32 prefix_end = m_nick_completion_end;
|
u32 prefix_end = m_nick_completion_end;
|
||||||
bool initial = (prefix_end == 0);
|
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
|
// no previous nick completion is active
|
||||||
prefix_start = prefix_end = m_cursor;
|
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;
|
--prefix_start;
|
||||||
while (prefix_end < m_line.size() && !iswspace(m_line[prefix_end]))
|
while (prefix_end < line.size() && !iswspace(line[prefix_end]))
|
||||||
++prefix_end;
|
++prefix_end;
|
||||||
if (prefix_start == prefix_end)
|
if (prefix_start == prefix_end)
|
||||||
return;
|
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
|
// find all names that start with the selected prefix
|
||||||
std::vector<std::wstring> completions;
|
std::vector<std::wstring> completions;
|
||||||
@ -576,9 +619,9 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa
|
|||||||
u32 replacement_index = 0;
|
u32 replacement_index = 0;
|
||||||
if (!initial)
|
if (!initial)
|
||||||
{
|
{
|
||||||
while (word_end < m_line.size() && !iswspace(m_line[word_end]))
|
while (word_end < line.size() && !iswspace(line[word_end]))
|
||||||
++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
|
// cycle through completions
|
||||||
for (u32 i = 0; i < completions.size(); ++i)
|
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];
|
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;
|
++word_end;
|
||||||
|
|
||||||
// replace existing word with replacement word,
|
// replace existing word with replacement word,
|
||||||
// place the cursor at the end and record the completion prefix
|
// 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();
|
m_cursor = prefix_start + replacement.size();
|
||||||
clampView();
|
clampView();
|
||||||
m_nick_completion_start = prefix_start;
|
m_nick_completion_start = prefix_start;
|
||||||
@ -616,7 +659,7 @@ void ChatPrompt::reformat(u32 cols)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
s32 length = m_line.size();
|
s32 length = getLineRef().size();
|
||||||
bool was_at_end = (m_view + m_cols >= length + 1);
|
bool was_at_end = (m_view + m_cols >= length + 1);
|
||||||
m_cols = cols - m_prompt.size();
|
m_cols = cols - m_prompt.size();
|
||||||
if (was_at_end)
|
if (was_at_end)
|
||||||
@ -627,7 +670,7 @@ void ChatPrompt::reformat(u32 cols)
|
|||||||
|
|
||||||
std::wstring ChatPrompt::getVisiblePortion() const
|
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 ChatPrompt::getVisibleCursorPosition() const
|
||||||
@ -640,7 +683,8 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
|
|||||||
s32 old_cursor = m_cursor;
|
s32 old_cursor = m_cursor;
|
||||||
s32 new_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;
|
s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
|
||||||
|
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
@ -650,17 +694,17 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
|
|||||||
case CURSOROP_SCOPE_WORD:
|
case CURSOROP_SCOPE_WORD:
|
||||||
if (dir == CURSOROP_DIR_RIGHT) {
|
if (dir == CURSOROP_DIR_RIGHT) {
|
||||||
// skip one word to the 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++;
|
new_cursor++;
|
||||||
while (new_cursor < length && !iswspace(m_line[new_cursor]))
|
while (new_cursor < length && !iswspace(line[new_cursor]))
|
||||||
new_cursor++;
|
new_cursor++;
|
||||||
while (new_cursor < length && iswspace(m_line[new_cursor]))
|
while (new_cursor < length && iswspace(line[new_cursor]))
|
||||||
new_cursor++;
|
new_cursor++;
|
||||||
} else {
|
} else {
|
||||||
// skip one word to the left
|
// 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--;
|
new_cursor--;
|
||||||
while (new_cursor >= 1 && !iswspace(m_line[new_cursor - 1]))
|
while (new_cursor >= 1 && !iswspace(line[new_cursor - 1]))
|
||||||
new_cursor--;
|
new_cursor--;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -680,10 +724,10 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
|
|||||||
break;
|
break;
|
||||||
case CURSOROP_DELETE:
|
case CURSOROP_DELETE:
|
||||||
if (m_cursor_len > 0) { // Delete selected text first
|
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 {
|
} else {
|
||||||
m_cursor = MYMIN(new_cursor, old_cursor);
|
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;
|
m_cursor_len = 0;
|
||||||
break;
|
break;
|
||||||
@ -707,7 +751,7 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
|
|||||||
|
|
||||||
void ChatPrompt::clampView()
|
void ChatPrompt::clampView()
|
||||||
{
|
{
|
||||||
s32 length = m_line.size();
|
s32 length = getLineRef().size();
|
||||||
if (length + 1 <= m_cols)
|
if (length + 1 <= m_cols)
|
||||||
{
|
{
|
||||||
m_view = 0;
|
m_view = 0;
|
||||||
|
24
src/chat.h
24
src/chat.h
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
|
|
||||||
#include "irrlichttypes.h"
|
#include "irrlichttypes.h"
|
||||||
#include "util/enriched_string.h"
|
#include "util/enriched_string.h"
|
||||||
|
#include "util/Optional.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
// Chat console related classes
|
// Chat console related classes
|
||||||
@ -172,10 +173,10 @@ public:
|
|||||||
void addToHistory(const std::wstring &line);
|
void addToHistory(const std::wstring &line);
|
||||||
|
|
||||||
// Get current 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
|
// 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
|
// Clear the current line
|
||||||
void clear();
|
void clear();
|
||||||
@ -233,18 +234,33 @@ public:
|
|||||||
void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);
|
void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
const std::wstring &getLineRef() const;
|
||||||
|
|
||||||
|
std::wstring &makeLineRef();
|
||||||
|
|
||||||
// set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols
|
// 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
|
// if line can be fully shown, set m_view to zero
|
||||||
// else, also ensure m_view <= m_line.size() + 1 - m_cols
|
// else, also ensure m_view <= m_line.size() + 1 - m_cols
|
||||||
void clampView();
|
void clampView();
|
||||||
|
|
||||||
private:
|
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
|
// Prompt prefix
|
||||||
std::wstring m_prompt = L"";
|
std::wstring m_prompt = L"";
|
||||||
// Currently edited line
|
// Non-historical edited line
|
||||||
std::wstring m_line = L"";
|
std::wstring m_line = L"";
|
||||||
// History buffer
|
// History buffer
|
||||||
std::vector<std::wstring> m_history;
|
std::vector<HistoryEntry> m_history;
|
||||||
// History index (0 <= m_history_index <= m_history.size())
|
// History index (0 <= m_history_index <= m_history.size())
|
||||||
u32 m_history_index = 0;
|
u32 m_history_index = 0;
|
||||||
// Maximum number of history entries
|
// Maximum number of history entries
|
||||||
|
Loading…
Reference in New Issue
Block a user